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 xbmcaddon
import json
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
from ClientInformation import ClientInformation
import urllib
import sys
import Utils as utils
import os
#define our global download utils
downloadUtils = DownloadUtils()
###########################################################################
class PlayUtils():
_shared_state = {}
clientInfo = ClientInformation()
###########################################################################
class PlayUtils():
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):
addon = self.addon
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
# if the path is local and depending on the video quality play we can direct play it do so-
if self.isDirectPlay(result) == True:
playurl = result.get("Path")
if playurl != None:
#We have a path to play so play it
USER_AGENT = 'QuickTime/7.7.4'
# If the file it is not a media stub
if (result.get("IsPlaceHolder") != True):
if (result.get("VideoType") == "Dvd"):
playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO"
elif (result.get("VideoType") == "BluRay"):
playurl = playurl + "/BDMV/index.bdmv"
if addonSettings.getSetting('smbusername') == '':
playurl = playurl.replace("\\\\", "smb://")
if self.isDirectPlay(result):
try:
# Try direct play
playurl = self.directPlay(result)
if not playurl:
# Let user know that direct play failed
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:
# Try direct stream
playurl = self.directStream(result, server, id)
addon.setSetting('playFromStream', "true")
if not playurl:
# Try transcoding
playurl = self.transcoding(result, server, id)
WINDOW.setProperty("transcoding%s" % id, "true")
self.logMsg("File is transcoding.", 1)
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("\\", "/")
if ("apple.com" in playurl):
playurl += '?|User-Agent=%s' % USER_AGENT
if addonSettings.getSetting('playFromStream') == "true":
if "apple.com" in playurl:
USER_AGENT = 'QuickTime/7.7.4'
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)
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:
#No path or has a path but not sufficient network so transcode
if result.get("Type") == "Audio":
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
else:
txt_mac = clientInfo.getMachineId()
mediaSources = result[u'MediaSources']
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
playurl = "%s&AudioStreamIndex=%s" % (playurl, mediaSources[0].get('DefaultAudioStreamIndex'))
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
playurl = "%s&SubtitleStreamIndex=%s" % (playurl, mediaSources[0].get('DefaultSubtitleStreamIndex'))
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 = playurl + '&videoCodec=h264'
playurl = playurl + '&AudioCodec=aac,ac3'
playurl = playurl + '&deviceId=' + txt_mac
playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000)
mediaSources = result.get("MediaSources")
if(mediaSources != None):
playurl = "%s&VideoCodec=h264&AudioCodec=aac,ac3&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
mediaSources = result[u'MediaSources']
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:
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultSubtitleStreamIndex'))
return playurl.encode('utf-8')
playurl = "%s&SubtitleStreamIndex=%s" % (playurl, mediaSources[0][u'DefaultSubtitleStreamIndex'])
# Works out if we are direct playing or not
def isDirectPlay(self, result):
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
self.logMsg("Playurl: %s" % playurl)
return playurl
except:
self.logMsg("Transcoding failed.")
return False
'''forceTranscodingCodecs = self.addon.getSetting('forceTranscodingCodecs')
# check if we should force encoding due to the forceTranscodingCodecs setting
forceTranscodingCodecs = addonSettings.getSetting('forceTranscodingCodecs')
if forceTranscodingCodecs:
forceTranscodingCodecsSet = frozenset(forceTranscodingCodecs.lower().split(','))
codecs = frozenset([mediaStream.get('Codec', None) for mediaStream in result.get('MediaStreams', [])])
commonCodecs = forceTranscodingCodecsSet & codecs
#xbmc.log("emby isDirectPlay MediaStreams codecs: %s forceTranscodingCodecs: %s, common: %s" % (codecs, forceTranscodingCodecsSet, commonCodecs))
if commonCodecs:
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
return False'''
# Works out if the network quality can play directly or if transcoding is needed
def isNetworkQualitySufficient(self, result):
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = int(settingsVideoBitRate) * 1000
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('Bitrate') != None:
if settingsVideoBitRate < int(mediaSources[0].get('Bitrate')):
#xbmc.log("emby isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate')))
settingsVideoBitRate = settingsVideoBitRate * 1000
try:
mediaSources = result[u'MediaSources']
sourceBitRate = int(mediaSources[0][u'Bitrate'])
if settingsVideoBitRate > sourceBitRate:
return True
else:
return False
else:
#xbmc.log("emby isNetworkQualitySufficient -> TRUE bit rate")
except:
return True
# Any thing else is ok
#xbmc.log("emby isNetworkQualitySufficient -> TRUE default")
return True
# get the addon video quality
def getVideoBitRate(self):
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
videoQuality = addonSettings.getSetting('videoBitRate')
# get the addon video quality
videoQuality = self.addon.getSetting('videoBitRate')
if (videoQuality == "0"):
return '664'
return 664
elif (videoQuality == "1"):
return '996'
return 996
elif (videoQuality == "2"):
return '1320'
return 1320
elif (videoQuality == "3"):
return '2000'
return 2000
elif (videoQuality == "4"):
return '3200'
return 3200
elif (videoQuality == "5"):
return '4700'
return 4700
elif (videoQuality == "6"):
return '6200'
return 6200
elif (videoQuality == "7"):
return '7700'
return 7700
elif (videoQuality == "8"):
return '9200'
return 9200
elif (videoQuality == "9"):
return '10700'
return 10700
elif (videoQuality == "10"):
return '12200'
return 12200
elif (videoQuality == "11"):
return '13700'
return 13700
elif (videoQuality == "12"):
return '15200'
return 15200
elif (videoQuality == "13"):
return '16700'
return 16700
elif (videoQuality == "14"):
return '18200'
return 18200
elif (videoQuality == "15"):
return '20000'
return 20000
elif (videoQuality == "16"):
return '40000'
return 40000
elif (videoQuality == "17"):
return '100000'
return 100000
elif (videoQuality == "18"):
return '1000000'
return 1000000
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):
if not result.has_key("Path"):
if u'Path' not in result:
# File has no path in server
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
else:
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
playurl = PlayUtils().getPlayUrl(server, id, result)
if playurl == False:
xbmc.log("Failed to retrieve the playback path/url.")
return
thumbPath = API().getArtwork(result, "Primary")
#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
#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()
# 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):
self.logMsg("stopPlayback called", 2)