Reworked playback

Supports local path, network path, direct streaming, transcoding.
This commit is contained in:
angelblue05 2015-05-07 01:11:20 -05:00
parent 22c62d9727
commit aa72b4ce9c
3 changed files with 259 additions and 136 deletions

View file

@ -6,182 +6,302 @@ import xbmc
import xbmcgui import xbmcgui
import xbmcaddon import xbmcaddon
import json
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
from ClientInformation import ClientInformation from ClientInformation import ClientInformation
import urllib import Utils as utils
import sys
import os import os
#define our global download utils
downloadUtils = DownloadUtils()
clientInfo = ClientInformation()
########################################################################### ###########################################################################
class PlayUtils(): class PlayUtils():
_shared_state = {}
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
addonId = clientInfo.getAddonId()
addon = xbmcaddon.Addon(id=addonId)
def __init__(self):
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl))
def getPlayUrl(self, server, id, result): def getPlayUrl(self, server, id, result):
addon = self.addon
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser') username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username) server = WINDOW.getProperty('server%s' % username)
addonSettings = xbmcaddon.Addon(id='plugin.video.emby') if self.isDirectPlay(result):
# if the path is local and depending on the video quality play we can direct play it do so- try:
if self.isDirectPlay(result) == True: # Try direct play
playurl = result.get("Path") playurl = self.directPlay(result)
if playurl != None: if not playurl:
#We have a path to play so play it # Let user know that direct play failed
USER_AGENT = 'QuickTime/7.7.4' resp = xbmcgui.Dialog().yesno('Warning', 'Unable to direct play. Try direct stream or transcoding instead? By selecting yes, it will also switch your playback to HTTP for future playback.')
if resp == True:
# If the file it is not a media stub # Try direct stream
if (result.get("IsPlaceHolder") != True): playurl = self.directStream(result, server, id)
if (result.get("VideoType") == "Dvd"): addon.setSetting('playFromStream', "true")
playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO" if not playurl:
elif (result.get("VideoType") == "BluRay"): # Try transcoding
playurl = playurl + "/BDMV/index.bdmv" playurl = self.transcoding(result, server, id)
if addonSettings.getSetting('smbusername') == '': WINDOW.setProperty("transcoding%s" % id, "true")
playurl = playurl.replace("\\\\", "smb://") self.logMsg("File is transcoding.", 1)
else: else:
playurl = playurl.replace("\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@') self.logMsg("File is direct streaming.", 1)
else:
# User decided not to proceed.
self.logMsg("Unable to direct play. Verify the following path is accessible by the device: %s. You might also need to add SMB credentials in the addon settings." % result[u'MediaSources'][0][u'Path'])
return False
else:
self.logMsg("File is direct playing.", 1)
except:
return False
elif self.isDirectStream(result):
try:
# Try direct stream
playurl = self.directStream(result, server, id)
if not playurl:
# Try transcoding
playurl = self.transcoding(result, server, id)
WINDOW.setProperty("transcoding%s" % id, "true")
self.logMsg("File is transcoding.", 1)
else:
self.logMsg("File is direct streaming.", 1)
except:
return False
elif self.isTranscoding(result):
try:
# Try transcoding
playurl = self.transcoding(result, server, id)
WINDOW.setProperty("transcoding%s" % id, "true")
self.logMsg("File is transcoding.", 1)
except:
return False
return playurl
def isDirectPlay(self, result):
# Requirements for Direct play:
# FileSystem, Accessible path
addon = self.addon
playhttp = addon.getSetting('playFromStream')
# User forcing to play via HTTP instead of SMB
if playhttp == "true":
return False
canDirectPlay = result[u'MediaSources'][0][u'SupportsDirectPlay']
# Make sure it's supported by server
if not canDirectPlay:
return False
location = result[u'LocationType']
# File needs to be "FileSystem"
if u'FileSystem' in location:
# Verify if path is accessible
if self.fileExists(result):
return True
def directPlay(self, result):
try:
# Item can be played directly
playurl = result[u'MediaSources'][0][u'Path']
if u'VideoType' in result:
# Specific format modification
if u'Dvd' in result[u'VideoType']:
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif u'BluRay' in result[u'VideoType']:
playurl = "%s/BDMV/index.bdmv" % playurl
# Network - SMB protocol
if "\\\\" in playurl:
smbuser = addon.getSetting('smbusername')
smbpass = addon.getSetting('smbpassword')
# Network share
if smbuser:
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/") playurl = playurl.replace("\\", "/")
if ("apple.com" in playurl): if "apple.com" in playurl:
playurl += '?|User-Agent=%s' % USER_AGENT USER_AGENT = 'QuickTime/7.7.4'
if addonSettings.getSetting('playFromStream') == "true": playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
except:
self.logMsg("Direct play failed. Trying Direct stream.", 1)
return False
def isDirectStream(self, result):
# Requirements for Direct stream:
# FileSystem or Remote, BitRate, supported encoding
canDirectStream = result[u'MediaSources'][0][u'SupportsDirectStream']
# Make sure it's supported by server
if not canDirectStream:
return False
location = result[u'LocationType']
# File can be FileSystem or Remote, not Virtual
if u'Virtual' in location:
return False
# Verify BitRate
if not self.isNetworkQualitySufficient(result):
return False
return True
def directStream(self, result, server, id):
try:
# Play with Direct Stream
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id) playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex'))
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex'))
else: mediaSources = result[u'MediaSources']
#No path or has a path but not sufficient network so transcode if mediaSources[0].get('DefaultAudioStreamIndex') != None:
if result.get("Type") == "Audio": playurl = "%s&AudioStreamIndex=%s" % (playurl, mediaSources[0].get('DefaultAudioStreamIndex'))
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id) if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
else: playurl = "%s&SubtitleStreamIndex=%s" % (playurl, mediaSources[0].get('DefaultSubtitleStreamIndex'))
txt_mac = clientInfo.getMachineId()
self.logMsg("Playurl: %s" % playurl)
return playurl
except:
self.logMsg("Direct stream failed. Trying transcoding.", 1)
return False
def isTranscoding(self, result):
# Last resort, no requirements
# BitRate
canTranscode = result[u'MediaSources'][0][u'SupportsTranscoding']
# Make sure it's supported by server
if not canTranscode:
return False
location = result[u'LocationType']
# File can be FileSystem or Remote, not Virtual
if u'Virtual' in location:
return False
return True
def transcoding(self, result, server, id):
try:
# Play transcoding
deviceId = self.clientInfo.getMachineId()
playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id) playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
playurl = playurl + '&videoCodec=h264' playurl = "%s&VideoCodec=h264&AudioCodec=aac,ac3&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
playurl = playurl + '&AudioCodec=aac,ac3'
playurl = playurl + '&deviceId=' + txt_mac mediaSources = result[u'MediaSources']
playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000)
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('DefaultAudioStreamIndex') != None: if mediaSources[0].get('DefaultAudioStreamIndex') != None:
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex')) playurl = "%s&AudioStreamIndex=%s" % (playurl, mediaSources[0][u'DefaultAudioStreamIndex'])
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultSubtitleStreamIndex')) playurl = "%s&SubtitleStreamIndex=%s" % (playurl, mediaSources[0][u'DefaultSubtitleStreamIndex'])
return playurl.encode('utf-8')
# Works out if we are direct playing or not self.logMsg("Playurl: %s" % playurl)
def isDirectPlay(self, result): return playurl
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
except:
self.logMsg("Transcoding failed.")
return False
'''forceTranscodingCodecs = self.addon.getSetting('forceTranscodingCodecs')
# check if we should force encoding due to the forceTranscodingCodecs setting # check if we should force encoding due to the forceTranscodingCodecs setting
forceTranscodingCodecs = addonSettings.getSetting('forceTranscodingCodecs')
if forceTranscodingCodecs: if forceTranscodingCodecs:
forceTranscodingCodecsSet = frozenset(forceTranscodingCodecs.lower().split(',')) forceTranscodingCodecsSet = frozenset(forceTranscodingCodecs.lower().split(','))
codecs = frozenset([mediaStream.get('Codec', None) for mediaStream in result.get('MediaStreams', [])]) codecs = frozenset([mediaStream.get('Codec', None) for mediaStream in result.get('MediaStreams', [])])
commonCodecs = forceTranscodingCodecsSet & codecs commonCodecs = forceTranscodingCodecsSet & codecs
#xbmc.log("emby isDirectPlay MediaStreams codecs: %s forceTranscodingCodecs: %s, common: %s" % (codecs, forceTranscodingCodecsSet, commonCodecs)) #xbmc.log("emby isDirectPlay MediaStreams codecs: %s forceTranscodingCodecs: %s, common: %s" % (codecs, forceTranscodingCodecsSet, commonCodecs))
if commonCodecs: if commonCodecs:
return False return False'''
if (self.fileExists(result) or (result.get("LocationType") == "FileSystem" and self.isNetworkQualitySufficient(result) == True and self.isLocalPath(result) == False)):
return True
else:
return False
# Works out if the network quality can play directly or if transcoding is needed # Works out if the network quality can play directly or if transcoding is needed
def isNetworkQualitySufficient(self, result): def isNetworkQualitySufficient(self, result):
settingsVideoBitRate = self.getVideoBitRate() settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = int(settingsVideoBitRate) * 1000 settingsVideoBitRate = settingsVideoBitRate * 1000
mediaSources = result.get("MediaSources")
if(mediaSources != None): try:
if mediaSources[0].get('Bitrate') != None: mediaSources = result[u'MediaSources']
if settingsVideoBitRate < int(mediaSources[0].get('Bitrate')): sourceBitRate = int(mediaSources[0][u'Bitrate'])
#xbmc.log("emby isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate')))
if settingsVideoBitRate > sourceBitRate:
return True
else:
return False return False
else: except:
#xbmc.log("emby isNetworkQualitySufficient -> TRUE bit rate")
return True return True
# Any thing else is ok
#xbmc.log("emby isNetworkQualitySufficient -> TRUE default")
return True
# get the addon video quality
def getVideoBitRate(self): def getVideoBitRate(self):
addonSettings = xbmcaddon.Addon(id='plugin.video.emby') # get the addon video quality
videoQuality = addonSettings.getSetting('videoBitRate') videoQuality = self.addon.getSetting('videoBitRate')
if (videoQuality == "0"): if (videoQuality == "0"):
return '664' return 664
elif (videoQuality == "1"): elif (videoQuality == "1"):
return '996' return 996
elif (videoQuality == "2"): elif (videoQuality == "2"):
return '1320' return 1320
elif (videoQuality == "3"): elif (videoQuality == "3"):
return '2000' return 2000
elif (videoQuality == "4"): elif (videoQuality == "4"):
return '3200' return 3200
elif (videoQuality == "5"): elif (videoQuality == "5"):
return '4700' return 4700
elif (videoQuality == "6"): elif (videoQuality == "6"):
return '6200' return 6200
elif (videoQuality == "7"): elif (videoQuality == "7"):
return '7700' return 7700
elif (videoQuality == "8"): elif (videoQuality == "8"):
return '9200' return 9200
elif (videoQuality == "9"): elif (videoQuality == "9"):
return '10700' return 10700
elif (videoQuality == "10"): elif (videoQuality == "10"):
return '12200' return 12200
elif (videoQuality == "11"): elif (videoQuality == "11"):
return '13700' return 13700
elif (videoQuality == "12"): elif (videoQuality == "12"):
return '15200' return 15200
elif (videoQuality == "13"): elif (videoQuality == "13"):
return '16700' return 16700
elif (videoQuality == "14"): elif (videoQuality == "14"):
return '18200' return 18200
elif (videoQuality == "15"): elif (videoQuality == "15"):
return '20000' return 20000
elif (videoQuality == "16"): elif (videoQuality == "16"):
return '40000' return 40000
elif (videoQuality == "17"): elif (videoQuality == "17"):
return '100000' return 100000
elif (videoQuality == "18"): elif (videoQuality == "18"):
return '1000000' return 1000000
else: else:
return '2147483' # max bit rate supported by server (max signed 32bit integer) return 2147483 # max bit rate supported by server (max signed 32bit integer)
def fileExists(self, result): def fileExists(self, result):
if not result.has_key("Path"):
if u'Path' not in result:
# File has no path in server
return False return False
path=result.get("Path").encode('utf-8')
if os.path.exists(path) == True: path = result[u'Path']
# Verify the device has access to the direct path
if os.path.exists(path):
return True return True
else: else:
return False return False
# Works out if the network quality can play directly or if transcoding is needed
def isLocalPath(self, result):
path=result.get("Path").encode('utf-8')
playurl = path
if playurl != None:
#We have a path to play so play it
if ":\\" in playurl:
return True
else:
return False
# default to not local
return False

View file

@ -57,6 +57,10 @@ class PlaybackUtils():
seekTime = reasonableTicks / 10000 seekTime = reasonableTicks / 10000
playurl = PlayUtils().getPlayUrl(server, id, result) playurl = PlayUtils().getPlayUrl(server, id, result)
if playurl == False:
xbmc.log("Failed to retrieve the playback path/url.")
return
thumbPath = API().getArtwork(result, "Primary") thumbPath = API().getArtwork(result, "Primary")
#if the file is a virtual strm file, we need to override the path by reading it's contents #if the file is a virtual strm file, we need to override the path by reading it's contents

View file

@ -95,16 +95,15 @@ class Player( xbmc.Player ):
#report updates playcount and resume status to Kodi and MB3 #report updates playcount and resume status to Kodi and MB3
#librarySync.updatePlayCount(item_id) #librarySync.updatePlayCount(item_id)
# Stop transcoding
if self.WINDOW.getProperty("transcoding%s" % item_id) == "true":
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.WINDOW.clearProperty("transcoding%s" % item_id)
self.played_information.clear() self.played_information.clear()
# stop transcoding - todo check we are actually transcoding?
clientInfo = ClientInformation()
txt_mac = clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings"
url = url + '?DeviceId=' + txt_mac
self.doUtils.downloadUrl(url, type="DELETE")
def stopPlayback(self, data): def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2) self.logMsg("stopPlayback called", 2)