Transcode revision, stack files, playback cleanup

This commit is contained in:
angelblue05 2015-10-01 08:08:34 -05:00
parent 0294957d14
commit 4ab6991968
5 changed files with 726 additions and 853 deletions

View file

@ -1,30 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#################################################################################################
# utils class
################################################################################################# #################################################################################################
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcaddon
import xbmcvfs import xbmcvfs
from ClientInformation import ClientInformation from ClientInformation import ClientInformation
import Utils as utils import Utils as utils
########################################################################### #################################################################################################
class PlayUtils(): class PlayUtils():
_shared_state = {}
clientInfo = ClientInformation() clientInfo = ClientInformation()
addonName = clientInfo.getAddonName() addonName = clientInfo.getAddonName()
WINDOW = xbmcgui.Window(10000)
def __init__(self):
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
@ -33,59 +23,51 @@ class PlayUtils():
def getPlayUrl(self, server, id, result): def getPlayUrl(self, server, id, result):
WINDOW = self.WINDOW
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
if self.isDirectPlay(result,True): if self.isDirectPlay(result,True):
# Try direct play # Try direct play
playurl = self.directPlay(result) playurl = self.directPlay(result)
if playurl: if playurl:
self.logMsg("File is direct playing.", 1) self.logMsg("File is direct playing.", 1)
WINDOW.setProperty("%splaymethod" % playurl.encode('utf-8'), "DirectPlay") utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay")
elif self.isDirectStream(result): elif self.isDirectStream(result):
# Try direct stream # Try direct stream
playurl = self.directStream(result, server, id) playurl = self.directStream(result, server, id)
if playurl: if playurl:
self.logMsg("File is direct streaming.", 1) self.logMsg("File is direct streaming.", 1)
WINDOW.setProperty("%splaymethod" % playurl, "DirectStream") utils.window("%splaymethod" % playurl, value="DirectStream")
elif self.isTranscoding(result): elif self.isTranscoding(result):
# Try transcoding # Try transcoding
playurl = self.transcoding(result, server, id) playurl = self.transcoding(result, server, id)
if playurl: if playurl:
self.logMsg("File is transcoding.", 1) self.logMsg("File is transcoding.", 1)
WINDOW.setProperty("%splaymethod" % playurl, "Transcode") utils.window("%splaymethod" % playurl, value="Transcode")
else:
# Error else: # Error
return False utils.window("playurlFalse", value="true")
return
return playurl.encode('utf-8') return playurl.encode('utf-8')
def isDirectPlay(self, result, dialog=False):
def isDirectPlay(self, result, dialog = False):
# Requirements for Direct play: # Requirements for Direct play:
# FileSystem, Accessible path # FileSystem, Accessible path
if utils.settings('playFromStream') == "true":
playhttp = utils.settings('playFromStream') # User forcing to play via HTTP instead of SMB
# User forcing to play via HTTP instead of SMB
if playhttp == "true":
self.logMsg("Can't direct play: Play from HTTP is enabled.", 1) self.logMsg("Can't direct play: Play from HTTP is enabled.", 1)
return False return False
canDirectPlay = result[u'MediaSources'][0][u'SupportsDirectPlay'] canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
# Make sure it's supported by server # Make sure it's supported by server
if not canDirectPlay: if not canDirectPlay:
self.logMsg("Can't direct play: Server does not allow or support it.", 1) self.logMsg("Can't direct play: Server does not allow or support it.", 1)
return False return False
if result['Path'].endswith('.strm'): location = result['LocationType']
# Allow strm loading when direct playing
return True
location = result[u'LocationType']
# File needs to be "FileSystem" # File needs to be "FileSystem"
if u'FileSystem' in location: if 'FileSystem' in location:
# Verify if path is accessible # Verify if path is accessible
if self.fileExists(result): if self.fileExists(result):
return True return True
@ -107,53 +89,49 @@ class PlayUtils():
return False return False
def directPlay(self, result): def directPlay(self, result):
try: try:
try: playurl = result['MediaSources'][0]['Path']
playurl = result[u'MediaSources'][0][u'Path'] except KeyError:
except: playurl = result['Path']
playurl = result[u'Path']
except:
self.logMsg("Direct play failed. Trying Direct stream.", 1)
return False
else:
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 'VideoType' in result:
if "\\\\" in playurl: # Specific format modification
smbuser = utils.settings('smbusername') if 'Dvd' in result['VideoType']:
smbpass = utils.settings('smbpassword') playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
# Network share elif 'BluRay' in result['VideoType']:
if smbuser: playurl = "%s/BDMV/index.bdmv" % playurl
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl: # Network - SMB protocol
USER_AGENT = "QuickTime/7.7.4" if "\\\\" in playurl:
playurl += "?|User-Agent=%s" % USER_AGENT smbuser = utils.settings('smbusername')
smbpass = utils.settings('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:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
return playurl
def isDirectStream(self, result): def isDirectStream(self, result):
# Requirements for Direct stream: # Requirements for Direct stream:
# FileSystem or Remote, BitRate, supported encoding # FileSystem or Remote, BitRate, supported encoding
canDirectStream = result[u'MediaSources'][0][u'SupportsDirectStream'] canDirectStream = result['MediaSources'][0]['SupportsDirectStream']
# Make sure it's supported by server # Make sure it's supported by server
if not canDirectStream: if not canDirectStream:
return False return False
location = result[u'LocationType'] location = result['LocationType']
# File can be FileSystem or Remote, not Virtual # File can be FileSystem or Remote, not Virtual
if u'Virtual' in location: if 'Virtual' in location:
self.logMsg("File location is virtual. Can't proceed.", 1) self.logMsg("File location is virtual. Can't proceed.", 1)
return False return False
@ -171,33 +149,29 @@ class PlayUtils():
playurl = self.directPlay(result) playurl = self.directPlay(result)
return playurl return playurl
try: if "ThemeVideo" in type:
if "ThemeVideo" in type: playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
playurl ="%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Video" in type: elif "Video" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id) playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Audio" in type: elif "Audio" in type:
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id) playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
return playurl return playurl
except:
self.logMsg("Direct stream failed. Trying transcoding.", 1)
return False
def isTranscoding(self, result): def isTranscoding(self, result):
# Last resort, no requirements # Last resort, no requirements
# BitRate # BitRate
canTranscode = result[u'MediaSources'][0][u'SupportsTranscoding'] canTranscode = result['MediaSources'][0]['SupportsTranscoding']
# Make sure it's supported by server # Make sure it's supported by server
if not canTranscode: if not canTranscode:
return False return False
location = result[u'LocationType'] location = result['LocationType']
# File can be FileSystem or Remote, not Virtual # File can be FileSystem or Remote, not Virtual
if u'Virtual' in location: if 'Virtual' in location:
return False return False
return True return True
@ -209,30 +183,27 @@ class PlayUtils():
playurl = self.directPlay(result) playurl = self.directPlay(result)
return playurl return playurl
try: # Play transcoding
# Play transcoding deviceId = self.clientInfo.getMachineId()
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 = "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
playurl = "%s&VideoCodec=h264&AudioCodec=ac3&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
self.logMsg("Playurl: %s" % playurl)
return playurl playurl = self.audioSubsPref(playurl, result.get('MediaSources'))
self.logMsg("Playurl: %s" % playurl, 1)
return playurl
except:
self.logMsg("Transcoding failed.")
return False
# Works out if the network quality can play directly or if transcoding is needed
def isNetworkQualitySufficient(self, result): def isNetworkQualitySufficient(self, result):
# Works out if the network quality can play directly or if transcoding is needed
settingsVideoBitRate = self.getVideoBitRate() settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = settingsVideoBitRate * 1000 settingsVideoBitRate = settingsVideoBitRate * 1000
try: try:
mediaSources = result[u'MediaSources'] mediaSources = result['MediaSources']
sourceBitRate = int(mediaSources[0][u'Bitrate']) sourceBitRate = int(mediaSources[0]['Bitrate'])
except: except KeyError:
self.logMsg("Bitrate value is missing.") self.logMsg("Bitrate value is missing.", 1)
else: else:
self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1) self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1)
if settingsVideoBitRate < sourceBitRate: if settingsVideoBitRate < sourceBitRate:
@ -243,58 +214,40 @@ class PlayUtils():
def getVideoBitRate(self): def getVideoBitRate(self):
# get the addon video quality # get the addon video quality
videoQuality = utils.settings('videoBitRate') videoQuality = utils.settings('videoBitRate')
bitrate = {
if (videoQuality == "0"): '0': 664,
return 664 '1': 996,
elif (videoQuality == "1"): '2': 1320,
return 996 '3': 2000,
elif (videoQuality == "2"): '4': 3200,
return 1320 '5': 4700,
elif (videoQuality == "3"): '6': 6200,
return 2000 '7': 7700,
elif (videoQuality == "4"): '8': 9200,
return 3200 '9': 10700,
elif (videoQuality == "5"): '10': 12200,
return 4700 '11': 13700,
elif (videoQuality == "6"): '12': 15200,
return 6200 '13': 16700,
elif (videoQuality == "7"): '14': 18200,
return 7700 '15': 20000,
elif (videoQuality == "8"): '16': 40000,
return 9200 '17': 100000,
elif (videoQuality == "9"): '18': 1000000
return 10700 }
elif (videoQuality == "10"):
return 12200 # max bit rate supported by server (max signed 32bit integer)
elif (videoQuality == "11"): return bitrate.get(videoQuality, 2147483)
return 13700
elif (videoQuality == "12"):
return 15200
elif (videoQuality == "13"):
return 16700
elif (videoQuality == "14"):
return 18200
elif (videoQuality == "15"):
return 20000
elif (videoQuality == "16"):
return 40000
elif (videoQuality == "17"):
return 100000
elif (videoQuality == "18"):
return 1000000
else:
return 2147483 # max bit rate supported by server (max signed 32bit integer)
def fileExists(self, result): def fileExists(self, result):
if u'Path' not in result: if 'Path' not in result:
# File has no path in server # File has no path in server
return False return False
# Convert Emby path to a path we can verify # Convert Emby path to a path we can verify
path = self.directPlay(result) path = self.directPlay(result)
if not path:
return False
try: try:
pathexists = xbmcvfs.exists(path) pathexists = xbmcvfs.exists(path)
@ -313,3 +266,86 @@ class PlayUtils():
else: else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2) self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
return False return False
def audioSubsPref(self, url, mediaSources):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
mediaStream = mediaSources[0].get('MediaStreams')
for stream in mediaStream:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream['ChannelLayout']
try:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
except:
track = "%s - %s %s" % (index, codec, channelLayout)
audioStreamsChannelsList[index] = stream['Channels']
audioStreamsList[track] = index
audioStreams.append(track)
elif 'Subtitle' in type:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
if resp > -1:
# User selected audio
selected = audioStreams[resp]
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex']
else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
if len(subtitleStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selectSubsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "")
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
if audioChannels > 2:
playurlprefs += "&AudioBitrate=384000"
else:
playurlprefs += "&AudioBitrate=192000"
return playurlprefs

View file

@ -1,190 +1,151 @@
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcplugin
import xbmcgui
import xbmcaddon
import urllib
import datetime import datetime
import time
import json as json import json as json
import inspect
import sys import sys
import xbmc
import xbmcaddon
import xbmcplugin
import xbmcgui
from API import API
from DownloadUtils import DownloadUtils from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils from PlayUtils import PlayUtils
from ReadKodiDB import ReadKodiDB from ClientInformation import ClientInformation
from ReadEmbyDB import ReadEmbyDB
import Utils as utils import Utils as utils
from API import API
import os
import xbmcvfs
addon = xbmcaddon.Addon() #################################################################################################
addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
WINDOW = xbmcgui.Window( 10000 )
class PlaybackUtils(): class PlaybackUtils():
settings = None clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString language = addon.getLocalizedString
logLevel = 0 addonName = clientInfo.getAddonName()
downloadUtils = DownloadUtils()
WINDOW.clearProperty('playurlFalse') username = utils.window('currUser')
userid = utils.window('userId%s' % username)
server = utils.window('server%s' % username)
def __init__(self, *args): def logMsg(self, msg, lvl=1):
pass
def PLAY(self, result, setup="service"): className = self.__class__.__name__
xbmc.log("PLAY Called") utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser') def PLAY(self, result, setup = "service"):
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
try: self.logMsg("PLAY Called", 1)
id = result["Id"]
except:
return
userData = result['UserData'] api = self.api
doUtils = self.doUtils
server = self.server
# BOOKMARK - RESUME POINT listItem = xbmcgui.ListItem()
timeInfo = API().getTimeInfo(result) id = result['Id']
jumpBackSec = int(utils.settings("resumeJumpBack")) userdata = result['UserData']
# Get the playurl - direct play, direct stream or transcoding
playurl = PlayUtils().getPlayUrl(server, id, result)
if utils.window('playurlFalse') == "true":
# Playurl failed - set in PlayUtils.py
utils.window('playurlFalse', clear=True)
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
# Resume point for widget only
timeInfo = api.getTimeInfo(result)
jumpBackSec = int(utils.settings('resumeJumpBack'))
seekTime = round(float(timeInfo.get('ResumeTime')), 6) seekTime = round(float(timeInfo.get('ResumeTime')), 6)
if seekTime > jumpBackSec: if seekTime > jumpBackSec:
# To avoid negative bookmark # To avoid negative bookmark
seekTime = seekTime - jumpBackSec seekTime = seekTime - jumpBackSec
itemsToPlay = [] # Show the additional resume dialog if launched from a widget
if xbmc.getCondVisibility('Window.IsActive(home)') and seekTime:
# Dialog presentation
displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
display_list = ["%s %s" % (self.language(30106), displayTime), self.language(30107)]
resume_result = xbmcgui.Dialog().select(self.language(30105), display_list)
if resume_result == 0:
# User selected to resume, append resume point to listitem
listItem.setProperty('StartOffset', str(seekTime))
elif resume_result > 0:
# User selected to start from beginning
seekTime = 0
elif resume_result < 0:
# User cancelled the dialog
self.logMsg("User cancelled resume dialog.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
# In order, intros, original item requested and any additional parts
playstack = []
# Check for intros # Check for intros
if seekTime == 0: if utils.settings('disableCinema') == "false" and not seekTime:
# if we have any play them when the movie/show is not being resumed # if we have any play them when the movie/show is not being resumed
# We can add the option right here
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
intros = self.downloadUtils.downloadUrl(url) intros = doUtils.downloadUrl(url)
if intros[u'TotalRecordCount'] == 0: if intros['TotalRecordCount'] != 0:
pass for intro in intros['Items']:
else: introPlayurl = PlayUtils().getPlayUrl(server, intro['Id'], intro)
for intro in intros[u'Items']: self.setProperties(introPlayurl, intro)
introId = intro[u'Id'] playstack.append(introPlayurl)
itemsToPlay.append(introId)
# Add original item # Add original item
itemsToPlay.append(id) playstack.append(playurl)
self.setProperties(playurl, result)
# For split movies mainArt = API().getArtwork(result, "Primary")
if u'PartCount' in result: listItem.setThumbnailImage(mainArt)
partcount = result[u'PartCount'] listItem.setIconImage(mainArt)
# Get additional parts/playurl
# Get additional parts/playurl
if result.get('PartCount'):
url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
parts = self.downloadUtils.downloadUrl(url) parts = doUtils.downloadUrl(url)
for part in parts[u'Items']: for part in parts['Items']:
partId = part[u'Id'] additionalPlayurl = PlayUtils().getPlayUrl(server, part['Id'], part)
itemsToPlay.append(partId) self.setProperties(additionalPlayurl, part)
playstack.append(additionalPlayurl)
if len(itemsToPlay) > 1:
# Let's play the playlist
playlist = self.AddToPlaylist(itemsToPlay)
if xbmc.getCondVisibility("Window.IsActive(home)"):
# Widget workaround
return xbmc.Player().play(playlist)
else:
# Can't pass a playlist to setResolvedUrl, so return False
# Wait for Kodi to process setResolved failure, then launch playlist
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem())
xbmc.sleep(10)
# Since we clear the original Kodi playlist, this is to prevent
# info dialog - can't play next item from showing up.
xbmc.executebuiltin('Dialog.Close(all,true)')
return xbmc.Player().play(playlist)
playurl = PlayUtils().getPlayUrl(server, id, result)
if playurl == False or WINDOW.getProperty('playurlFalse') == "true":
WINDOW.clearProperty('playurlFalse')
xbmc.log("Failed to retrieve the playback path/url.")
return
if WINDOW.getProperty("%splaymethod" % playurl) == "Transcode":
# Transcoding, we pull every track to set before playback starts
playurlprefs = self.audioSubsPref(playurl, result.get("MediaSources"))
if playurlprefs:
playurl = playurlprefs
else: # User cancelled dialog
return
thumbPath = API().getArtwork(result, "Primary") if len(playstack) > 1:
# Convert list in stack:// playurl
playMethod = utils.window('%splaymethod' % playurl)
playurl = "stack://%s" % " , ".join(playstack)
# Set new window properties for combined playurl
utils.window("%splaymethod" % playurl, value=playMethod)
self.setProperties(playurl, result)
#if the file is a virtual strm file, we need to override the path by reading it's contents self.logMsg("Returned playurl: %s" % playurl, 1)
if playurl.endswith(".strm"): listItem.setPath(playurl)
xbmc.log("virtual strm file file detected, starting playback with 3th party addon...")
StrmTemp = "special://temp/temp.strm"
xbmcvfs.copy(playurl, StrmTemp)
playurl = open(xbmc.translatePath(StrmTemp), 'r').readline()
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath) if utils.window("%splaymethod" % playurl) != "Transcode":
if WINDOW.getProperty("%splaymethod" % playurl) != "Transcode":
# Only for direct play and direct stream # Only for direct play and direct stream
# Append external subtitles to stream # Append external subtitles to stream
subtitleList = self.externalSubs(id, playurl, server, result.get('MediaSources')) subtitleList = self.externalSubs(id, playurl, server, result['MediaSources'])
listItem.setSubtitles(subtitleList) listItem.setSubtitles(subtitleList)
#pass
# Can not play virtual items
if (result.get("LocationType") == "Virtual"):
xbmcgui.Dialog().ok(self.language(30128), self.language(30129))
watchedurl = "%s/mediabrowser/Users/%s/PlayedItems/%s" % (server, userid, id) # Launch the playback - only set the listitem props if we're not using the setresolvedurl approach
positionurl = "%s/mediabrowser/Users/%s/PlayingItems/%s" % (server, userid, id) if setup == "service" or xbmc.getCondVisibility('Window.IsActive(home)'):
deleteurl = "%s/mediabrowser/Items/%s" % (server, id) # Sent via websocketclient.py or default.py but via widgets
# set the current playing info
WINDOW.setProperty(playurl+"watchedurl", watchedurl)
WINDOW.setProperty(playurl+"positionurl", positionurl)
WINDOW.setProperty(playurl+"deleteurl", "")
WINDOW.setProperty(playurl+"deleteurl", deleteurl)
#show the additional resume dialog if launched from a widget
if xbmc.getCondVisibility("Window.IsActive(home)"):
if seekTime != 0:
displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
display_list = [ self.language(30106) + ' ' + displayTime, self.language(30107)]
resumeScreen = xbmcgui.Dialog()
resume_result = resumeScreen.select(self.language(30105), display_list)
if resume_result == 0:
listItem.setProperty('StartOffset', str(seekTime))
elif resume_result < 0:
# User cancelled dialog
xbmc.log("Emby player -> User cancelled resume dialog.")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
return
if result.get("Type")=="Episode":
WINDOW.setProperty(playurl+"refresh_id", result.get("SeriesId"))
else:
WINDOW.setProperty(playurl+"refresh_id", id)
WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks")))
WINDOW.setProperty(playurl+"type", result.get("Type"))
WINDOW.setProperty(playurl+"item_id", id)
#launch the playback - only set the listitem props if we're not using the setresolvedurl approach
if setup == "service":
self.setListItemProps(server, id, listItem, result) self.setListItemProps(server, id, listItem, result)
xbmc.Player().play(playurl,listItem) xbmc.Player().play(playurl, listItem)
elif setup == "default": elif setup == "default":
if xbmc.getCondVisibility("Window.IsActive(home)"): # Sent via default.py
self.setListItemProps(server, id, listItem, result) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
xbmc.Player().play(playurl,listItem)
else:
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
def externalSubs(self, id, playurl, server, mediaSources): def externalSubs(self, id, playurl, server, mediaSources):
@ -214,100 +175,72 @@ class PlaybackUtils():
kodiindex += 1 kodiindex += 1
mapping = json.dumps(mapping) mapping = json.dumps(mapping)
utils.window('%sIndexMapping' % playurl, mapping) utils.window('%sIndexMapping' % playurl, value=mapping)
return externalsubs return externalsubs
def audioSubsPref(self, url, mediaSources): def setProperties(self, playurl, result):
# Set runtimeticks, type, refresh_id and item_id
id = result.get('Id')
type = result.get('Type', "")
WINDOW = xbmcgui.Window(10000) utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
# Present the list of audio to select from utils.window("%stype" % playurl, value=type)
audioStreamsList = {} utils.window("%sitem_id" % playurl, value=id)
audioStreams = []
selectAudioIndex = ""
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectSubsIndex = ""
playurlprefs = "%s" % url
mediaStream = mediaSources[0].get('MediaStreams') if type == "Episode":
for stream in mediaStream: utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
index = stream['Index'] else:
# Since Emby returns all possible tracks together, have to sort them. utils.window("%srefresh_id" % playurl, value=id)
if 'Audio' in stream['Type']:
try:
track = stream['Language']
audioStreamsList[track] = index
audioStreams.append(track)
except:
track = stream['Codec']
audioStreamsList[track] = index
audioStreams.append(track)
elif 'Subtitle' in stream['Type']: def setArt(self, list, name, path):
try:
track = stream['Language']
subtitleStreamsList[track] = index
subtitleStreams.append(track)
except:
track = stream['Codec']
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1: if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
if resp > -1:
# User selected audio
selected = audioStreams[resp]
selected_audioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selected_audioIndex
selectAudioIndex = str(selected_audioIndex)
else: return False
else: # There's only one audiotrack.
audioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % audioIndex
selectAudioIndex = str(audioIndex)
if len(subtitleStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selected_subsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selected_subsIndex
selectSubsIndex = str(selected_subsIndex)
else: return False
# Reset the method with the new playurl
WINDOW.setProperty("%splaymethod" % playurlprefs, "Transcode")
WINDOW.setProperty("%sAudioStreamIndex" % playurlprefs, selectAudioIndex)
WINDOW.setProperty("%sSubtitleStreamIndex" % playurlprefs, selectSubsIndex)
return playurlprefs
def setArt(self, list,name,path):
if name=='thumb' or name=='fanart_image' or name=='small_poster' or name=='tiny_poster' or name == "medium_landscape" or name=='medium_poster' or name=='small_fanartimage' or name=='medium_fanartimage' or name=='fanart_noindicators':
list.setProperty(name, path) list.setProperty(name, path)
else: else:
list.setArt({name:path}) list.setArt({name:path})
return list return list
def setListItemProps(self, server, id, listItem, result): def setListItemProps(self, server, id, listItem, result):
# set up item and item info # Set up item and item info
thumbID = id api = self.api
eppNum = -1
seasonNum = -1
tvshowTitle = ""
if(result.get("Type") == "Episode"): type = result.get('Type')
thumbID = result.get("SeriesId") people = api.getPeople(result)
seasonNum = result.get("ParentIndexNumber") studios = api.getStudios(result)
eppNum = result.get("IndexNumber")
tvshowTitle = result.get("SeriesName")
metadata = {
'title': result.get('Name', "Missing name"),
'year': result.get('ProductionYear'),
'plot': api.getOverview(result),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': api.getMpaa(result),
'genre': api.getGenre(result),
'studio': " / ".join(studios),
'aired': api.getPremiereDate(result),
'rating': result.get('CommunityRating'),
'votes': result.get('VoteCount')
}
if "Episode" in type:
# Only for tv shows
thumbId = result.get('SeriesId')
season = result.get('ParentIndexNumber', -1)
episode = result.get('IndexNumber', -1)
show = result.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
metadata['episode'] = episode
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.setInfo('video', infoLabels=metadata)
# Set artwork for listitem
self.setArt(listItem,'poster', API().getArtwork(result, "Primary")) self.setArt(listItem,'poster', API().getArtwork(result, "Primary"))
self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary")) self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary"))
self.setArt(listItem,'clearart', API().getArtwork(result, "Art")) self.setArt(listItem,'clearart', API().getArtwork(result, "Art"))
@ -318,105 +251,94 @@ class PlaybackUtils():
self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop")) self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop"))
self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb")) self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
# Process Studios
studios = API().getStudios(result)
if studios == []:
studio = ""
else:
studio = studios[0]
listItem.setInfo('video', {'studio' : studio})
details = {
'title' : result.get("Name", "Missing Name"),
'plot' : result.get("Overview")
}
if(eppNum > -1):
details["episode"] = str(eppNum)
if(seasonNum > -1):
details["season"] = str(seasonNum)
if tvshowTitle != None:
details["TVShowTitle"] = tvshowTitle
listItem.setInfo( "Video", infoLabels=details )
people = API().getPeople(result)
# Process Genres
genre = API().getGenre(result)
listItem.setInfo('video', {'director' : people.get('Director')})
listItem.setInfo('video', {'writer' : people.get('Writer')})
listItem.setInfo('video', {'mpaa': result.get("OfficialRating")})
listItem.setInfo('video', {'genre': API().getGenre(result)})
def seekToPosition(self, seekTo): def seekToPosition(self, seekTo):
# Set a loop to wait for positive confirmation of playback
#Set a loop to wait for positive confirmation of playback
count = 0 count = 0
while not xbmc.Player().isPlaying(): while not xbmc.Player().isPlaying():
count = count + 1 count += 1
if count >= 10: if count >= 10:
return return
else: else:
xbmc.sleep(500) xbmc.sleep(500)
#Jump to resume point # Jump to seek position
jumpBackSec = int(addon.getSetting("resumeJumpBack"))
seekToTime = seekTo - jumpBackSec
count = 0 count = 0
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
count = count + 1 count += 1
#xbmc.Player().pause xbmc.Player().seekTime(seekTo)
#xbmc.sleep(100)
xbmc.Player().seekTime(seekToTime)
xbmc.sleep(100) xbmc.sleep(100)
#xbmc.Player().play()
def PLAYAllItems(self, items, startPositionTicks): def PLAYAllItems(self, items, startPositionTicks):
utils.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
utils.logMsg("PlayBackUtils", "Items : " + str(items))
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser') self.logMsg("== ENTER: PLAYAllItems ==")
userid = WINDOW.getProperty('userId%s' % username) self.logMsg("Items: %s" % items)
server = WINDOW.getProperty('server%s' % username)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) doUtils = self.doUtils
playlist = xbmc.Playlist(xbmc.PLAYLIST_VIDEO)
playlist.clear() playlist.clear()
started = False started = False
for itemID in items: for itemId in items:
self.logMsg("Adding Item to playlist: %s" % itemId, 1)
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
result = doUtils.downloadUrl(url)
utils.logMsg("PlayBackUtils", "Adding Item to Playlist : " + itemID) addition = self.addPlaylistItem(playlist, result)
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemID if not started and addition:
jsonData = self.downloadUtils.downloadUrl(item_url)
item_data = jsonData
added = self.addPlaylistItem(playlist, item_data, server, userid)
if(added and started == False):
started = True started = True
utils.logMsg("PlayBackUtils", "Starting Playback Pre") self.logMsg("Starting Playback Pre", 1)
xbmc.Player().play(playlist) xbmc.Player().play(playlist)
if(started == False): if not started:
utils.logMsg("PlayBackUtils", "Starting Playback Post") self.logMsg("Starting Playback Post", 1)
xbmc.Player().play(playlist) xbmc.Player().play(playlist)
#seek to position # Seek to position
seekTime = 0 if startPositionTicks:
if(startPositionTicks != None): seekTime = startPositionTicks / 10000000.0
seekTime = (startPositionTicks / 1000) / 10000
if seekTime > 0:
self.seekToPosition(seekTime) self.seekToPosition(seekTime)
def PLAYAllEpisodes(self, items): def AddToPlaylist(self, itemIds):
self.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
doUtils = self.doUtils
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for itemId in itemIds:
self.logMsg("Adding Item to Playlist: %s" % itemId)
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
result = doUtils.downloadUrl(url)
self.addPlaylistItem(playlist, result)
return playlist
def addPlaylistItem(self, playlist, item):
id = item['Id']
server = self.server
playurl = PlayUtils().getPlayUrl(server, id, item)
if utils.window('playurlFalse') == "true":
# Playurl failed - set in PlayUtils.py
utils.window('playurlFalse', clear=True)
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
return
self.logMsg("Playurl: %s" % playurl)
thumb = API().getArtwork(item, "Primary")
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumb, thumbnailImage=thumb)
self.setListItemProps(server, id, listItem, item)
self.setProperties(playurl, item)
playlist.add(playurl, listItem)
# Not currently being used
'''def PLAYAllEpisodes(self, items):
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser') username = WINDOW.getProperty('currUser')
@ -434,80 +356,4 @@ class PlaybackUtils():
item_data = jsonData item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid) self.addPlaylistItem(playlist, item_data, server, userid)
xbmc.Player().play(playlist) xbmc.Player().play(playlist)'''
def AddToPlaylist(self, itemIds):
utils.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
for itemID in itemIds:
utils.logMsg("PlayBackUtils", "Adding Item to Playlist : " + itemID)
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemID
jsonData = self.downloadUtils.downloadUrl(item_url)
item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid)
return playlist
def addPlaylistItem(self, playlist, item, server, userid):
id = item.get("Id")
playurl = PlayUtils().getPlayUrl(server, id, item)
utils.logMsg("PlayBackUtils", "Play URL: " + playurl)
thumbPath = API().getArtwork(item, "Primary")
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath)
self.setListItemProps(server, id, listItem, item)
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
# Can not play virtual items
if (item.get("LocationType") == "Virtual") or (item.get("IsPlaceHolder") == True):
xbmcgui.Dialog().ok(self.language(30128), self.language(30129))
return False
else:
watchedurl = "%s/mediabrowser/Users/%s/PlayedItems/%s" % (server, userid, id)
positionurl = "%s/mediabrowser/Users/%s/PlayingItems/%s" % (server, userid, id)
deleteurl = "%s/mediabrowser/Items/%s" % (server, id)
# set the current playing info
WINDOW = xbmcgui.Window( 10000 )
WINDOW.setProperty(playurl + "watchedurl", watchedurl)
WINDOW.setProperty(playurl + "positionurl", positionurl)
WINDOW.setProperty(playurl + "deleteurl", "")
if item.get("Type") == "Episode" and addon.getSetting("offerDeleteTV")=="true":
WINDOW.setProperty(playurl + "deleteurl", deleteurl)
if item.get("Type") == "Movie" and addon.getSetting("offerDeleteMovies")=="true":
WINDOW.setProperty(playurl + "deleteurl", deleteurl)
WINDOW.setProperty(playurl + "runtimeticks", str(item.get("RunTimeTicks")))
WINDOW.setProperty(playurl+"type", item.get("Type"))
WINDOW.setProperty(playurl + "item_id", id)
if (item.get("Type") == "Episode"):
WINDOW.setProperty(playurl + "refresh_id", item.get("SeriesId"))
else:
WINDOW.setProperty(playurl + "refresh_id", id)
utils.logMsg("PlayBackUtils", "PlayList Item Url : " + str(playurl))
playlist.add(playurl, listItem)
return True

View file

@ -1,27 +1,20 @@
import xbmcaddon # -*- coding: utf-8 -*-
import xbmcplugin
#################################################################################################
import json as json
import xbmc import xbmc
import xbmcgui import xbmcgui
import os
import threading
import json
import inspect
import KodiMonitor
import Utils as utils
from DownloadUtils import DownloadUtils from DownloadUtils import DownloadUtils
from WebSocketClient import WebSocketThread from WebSocketClient import WebSocketThread
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation from ClientInformation import ClientInformation
from LibrarySync import LibrarySync from LibrarySync import LibrarySync
from PlaybackUtils import PlaybackUtils import Utils as utils
from ReadEmbyDB import ReadEmbyDB
from API import API
librarySync = LibrarySync() #################################################################################################
# service class for playback monitoring
class Player( xbmc.Player ): class Player( xbmc.Player ):
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
@ -31,135 +24,206 @@ class Player( xbmc.Player ):
doUtils = DownloadUtils() doUtils = DownloadUtils()
clientInfo = ClientInformation() clientInfo = ClientInformation()
ws = WebSocketThread() ws = WebSocketThread()
librarySync = LibrarySync()
addonName = clientInfo.getAddonName() addonName = clientInfo.getAddonName()
WINDOW = xbmcgui.Window(10000)
logLevel = 0
played_information = {} played_information = {}
settings = None
playStats = {} playStats = {}
currentFile = None
stackFiles = None
stackElapsed = 0
audioPref = "default" def __init__(self, *args):
subsPref = "default"
def __init__( self, *args ):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
self.logMsg("Starting playback monitor service", 1) self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__ self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def setAudioSubsPref(self, audio, subs): def GetPlayStats(self):
self.audioPref = audio return self.playStats
self.subsPref = subs
def hasData(self, data): def currentStackItem(self, stackItems):
if(data == None or len(data) == 0 or data == "None"): # Only for stacked items - stack://
return False xbmcplayer = self.xbmcplayer
stack = stackItems.replace("stack://", "").split(" , ")
position = xbmcplayer.getTime()
totalRuntime = 0
for item in stack:
runtime = int(utils.window("%sruntimeticks" % item)) / 10000000
# Verify the position compared to the totalRuntime for stacked items processed in loop so far.
if position < (runtime + totalRuntime):
self.stackElapsed = totalRuntime
self.currentFile = item
return item
else:
totalRuntime += runtime
def onPlayBackStarted( self ):
# Will be called when xbmc starts playing a file
xbmcplayer = self.xbmcplayer
self.stopAll()
# Get current file - if stack://, get currently playing item
currentFile = xbmcplayer.getPlayingFile()
if "stack://" in currentFile:
self.stackFiles = currentFile
currentFile = self.currentStackItem(currentFile)
else: else:
return True self.stackFiles = None
self.currentFile = currentFile
self.stackElapsed = 0
def stopAll(self): self.logMsg("ONPLAYBACK_STARTED: %s" % currentFile, 0)
WINDOW = xbmcgui.Window(10000) # We may need to wait for info to be set in kodi monitor
itemId = utils.window("%sitem_id" % currentFile)
tryCount = 0
while not itemId:
if(len(self.played_information) == 0): xbmc.sleep(500)
return itemId = utils.window("%sitem_id" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
break
else: tryCount += 1
self.logMsg("emby Service -> played_information : " + str(self.played_information)) else:
# Only proceed if an itemId was found.
runtime = utils.window("%sruntimeticks" % currentFile)
refresh_id = utils.window("%srefresh_id" % currentFile)
playMethod = utils.window("%splaymethod" % currentFile)
itemType = utils.window("%stype" % currentFile)
mapping = utils.window("%sIndexMapping" % currentFile)
seekTime = xbmc.Player().getTime()
for item_url in self.played_information: self.logMsg("Mapping for subtitles index: %s" % mapping, 2)
data = self.played_information.get(item_url)
if (data is not None):
self.logMsg("emby Service -> item_url : " + item_url)
self.logMsg("emby Service -> item_data : " + str(data))
runtime = data.get("runtime") # Get playback volume
currentPosition = data.get("currentPosition") volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
item_id = data.get("item_id") result = xbmc.executeJSONRPC(volume_query)
refresh_id = data.get("refresh_id") result = json.loads(result)
currentFile = data.get("currentfile") volume = result.get('result').get('volume')
type = data.get("Type") muted = result.get('result').get('muted')
playMethod = data.get('playmethod')
# Prevent websocket feedback url = "{server}/mediabrowser/Sessions/Playing"
self.WINDOW.setProperty("played_itemId", item_id) postdata = {
if(currentPosition != None and self.hasData(runtime)): 'QueueableMediaTypes': "Video",
runtimeTicks = int(runtime) 'CanSeek': True,
self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks)) 'ItemId': itemId,
percentComplete = (currentPosition * 10000000) / runtimeTicks 'MediaSourceId': itemId,
markPlayedAt = float(90) / 100 'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000) - int(self.stackElapsed * 10000000),
'IsMuted': muted
}
self.logMsg("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) # Get the current audio track and subtitles
if percentComplete < markPlayedAt: if playMethod == "Transcode":
# Do not mark as watched # property set in PlayUtils.py
self.WINDOW.setProperty('played_skipWatched', 'true') postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile)
self.stopPlayback(data) else:
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
offerDelete=False # Audio tracks
if data.get("Type") == "Episode" and utils.settings("offerDeleteTV")=="true": indexAudio = result.get('result', 0)
offerDelete = True if indexAudio:
elif data.get("Type") == "Movie" and utils.settings("offerDeleteMovies")=="true": indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0)
offerDelete = True # Subtitles tracks
indexSubs = result.get('result', 0)
if indexSubs:
indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0)
# If subtitles are enabled
subsEnabled = result.get('result', "")
if subsEnabled:
subsEnabled = subsEnabled.get('subtitleenabled', "")
if percentComplete > .80 and offerDelete == True: # Postdata for the audio and subs tracks
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete\n" + data.get("currentfile").split("/")[-1] + "\non Emby Server? ") audioTracks = len(xbmc.Player().getAvailableAudioStreams())
if return_value: postdata['AudioStreamIndex'] = indexAudio + 1
# Delete Kodi entry before Emby
listItem = [item_id]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % item_id)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.played_information.clear() if mapping:
externalIndex = json.loads(mapping)
else: # Direct paths scenario
externalIndex = ""
def stopPlayback(self, data): if externalIndex:
# If there's external subtitles added via PlaybackUtils
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
external = len(externalIndex)
postdata['SubtitleStreamIndex'] = indexSubs - external + audioTracks + 1
else:
# No external subtitles added via PlayUtils
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
self.logMsg("stopPlayback called", 2) # Post playback to server
self.logMsg("Sending POST play started.", 1)
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
item_id = data.get("item_id") # save data map for updates and position calls
currentPosition = data.get("currentPosition") data = {
positionTicks = int(currentPosition * 10000000)
url = "{server}/mediabrowser/Sessions/Playing/Stopped" 'runtime': runtime,
'item_id': itemId,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime) - int(self.stackElapsed)
}
postdata = { self.played_information[currentFile] = data
'ItemId': item_id, self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
'MediaSourceId': item_id,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST") # log some playback stats
'''if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1'''
def reportPlayback(self): def reportPlayback(self):
self.logMsg("reportPlayback Called", 2) self.logMsg("reportPlayback Called", 2)
xbmcplayer = self.xbmcplayer xbmcplayer = self.xbmcplayer
if not xbmcplayer.isPlaying(): # Get current file
self.logMsg("reportPlayback: Not playing anything so returning", 0) currentFile = self.currentFile
return
currentFile = xbmcplayer.getPlayingFile()
data = self.played_information.get(currentFile) data = self.played_information.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value) # only report playback if emby has initiated the playback (item_id has value)
if data is not None and data.get("item_id") is not None: if data:
# Get playback information # Get playback information
item_id = data.get("item_id") itemId = data.get("item_id")
audioindex = data.get("AudioStreamIndex") audioindex = data.get("AudioStreamIndex")
subtitleindex = data.get("SubtitleStreamIndex") subtitleindex = data.get("SubtitleStreamIndex")
playTime = data.get("currentPosition") playTime = data.get("currentPosition")
@ -173,14 +237,15 @@ class Player( xbmc.Player ):
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query) result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result) result = json.loads(result)
volume = result.get(u'result').get(u'volume') volume = result.get('result').get('volume')
muted = result.get(u'result').get(u'muted') muted = result.get('result').get('muted')
postdata = { postdata = {
'QueueableMediaTypes': "Video", 'QueueableMediaTypes': "Video",
'CanSeek': True, 'CanSeek': True,
'ItemId': item_id, 'ItemId': itemId,
'MediaSourceId': item_id, 'MediaSourceId': itemId,
'PlayMethod': playMethod, 'PlayMethod': playMethod,
'IsPaused': paused, 'IsPaused': paused,
'VolumeLevel': volume, 'VolumeLevel': volume,
@ -188,9 +253,14 @@ class Player( xbmc.Player ):
} }
if playTime: if playTime:
postdata['PositionTicks'] = int(playTime * 10000000) postdata['PositionTicks'] = int(playTime * 10000000) - int(self.stackElapsed * 10000000)
if playMethod != "Transcode": if playMethod == "Transcode":
data['AudioStreamIndex'] = audioindex
data['SubtitleStreamIndex'] = subtitleindex
else:
# Get current audio and subtitles track # Get current audio and subtitles track
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query) result = xbmc.executeJSONRPC(track_query)
@ -249,208 +319,127 @@ class Player( xbmc.Player ):
postdata['SubtitleStreamIndex'] = indexSubs postdata['SubtitleStreamIndex'] = indexSubs
data['SubtitleStreamIndex'] = indexSubs data['SubtitleStreamIndex'] = indexSubs
else:
data['AudioStreamIndex'] = audioindex
data['SubtitleStreamIndex'] = subtitleindex
postdata = json.dumps(postdata) postdata = json.dumps(postdata)
self.logMsg("Report: %s" % postdata, 2) self.logMsg("Report: %s" % postdata, 2)
self.ws.sendProgressUpdate(postdata) self.ws.sendProgressUpdate(postdata)
def onPlayBackPaused( self ): def onPlayBackPaused( self ):
currentFile = xbmc.Player().getPlayingFile()
self.logMsg("PLAYBACK_PAUSED : " + currentFile,2) currentFile = self.currentFile
if(self.played_information.get(currentFile) != None): self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
self.played_information[currentFile]["paused"] = "true"
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = "true"
self.reportPlayback() self.reportPlayback()
def onPlayBackResumed( self ): def onPlayBackResumed( self ):
currentFile = xbmc.Player().getPlayingFile()
self.logMsg("PLAYBACK_RESUMED : " + currentFile,2) currentFile = self.currentFile
if(self.played_information.get(currentFile) != None): self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
self.played_information[currentFile]["paused"] = "false"
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = "false"
self.reportPlayback() self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ): def onPlayBackSeek( self, time, seekOffset ):
self.logMsg("PLAYBACK_SEEK",2)
# Make position when seeking a bit more accurate
try:
playTime = xbmc.Player().getTime()
currentFile = xbmc.Player().getPlayingFile()
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["currentPosition"] = playTime
except: pass
self.reportPlayback()
def onPlayBackStarted( self ): self.logMsg("PLAYBACK_SEEK", 2)
# Will be called when xbmc starts playing a file
WINDOW = xbmcgui.Window(10000)
xbmcplayer = self.xbmcplayer xbmcplayer = self.xbmcplayer
self.stopAll() # Make position when seeking a bit more accurate
position = xbmcplayer.getTime()
currentFile = self.currentFile
if xbmcplayer.isPlaying(): if self.played_information.get(currentFile):
self.played_information[currentFile]['currentPosition'] = position
currentFile = "" self.reportPlayback()
try:
currentFile = xbmcplayer.getPlayingFile()
except: pass
self.logMsg("onPlayBackStarted: %s" % currentFile, 0)
# we may need to wait until the info is available
item_id = WINDOW.getProperty(currentFile + "item_id")
tryCount = 0
while(item_id == None or item_id == ""):
xbmc.sleep(500)
item_id = WINDOW.getProperty(currentFile + "item_id")
tryCount += 1
if(tryCount == 20): # try 20 times or about 10 seconds
return
xbmc.sleep(500)
# grab all the info about this item from the stored windows props
# only ever use the win props here, use the data map in all other places
runtime = WINDOW.getProperty(currentFile + "runtimeticks")
refresh_id = WINDOW.getProperty(currentFile + "refresh_id")
playMethod = WINDOW.getProperty(currentFile + "playmethod")
itemType = WINDOW.getProperty(currentFile + "type")
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
self.logMsg("Mapping for index: %s" % mapping)
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
volume = result.get(u'result').get(u'volume')
muted = result.get(u'result').get(u'muted')
seekTime = xbmc.Player().getTime()
url = "{server}/mediabrowser/Sessions/Playing"
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': item_id,
'MediaSourceId': item_id,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex")
subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex")
postdata['AudioStreamIndex'] = audioindex
postdata['SubtitleStreamIndex'] = subtitleindex
else:
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
# Audio tracks
indexAudio = result.get('result', 0)
if indexAudio:
indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0)
# Subtitles tracks
indexSubs = result.get('result', 0)
if indexSubs:
indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0)
# If subtitles are enabled
subsEnabled = result.get('result', "")
if subsEnabled:
subsEnabled = subsEnabled.get('subtitleenabled', "")
# Postdata for the audio and subs tracks
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
postdata['AudioStreamIndex'] = indexAudio + 1
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
if mapping:
externalIndex = json.loads(mapping)
else: # Direct paths scenario
externalIndex = ""
if externalIndex:
# If there's external subtitles added via PlaybackUtils
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
external = len(externalIndex)
postdata['SubtitleStreamIndex'] = indexSubs - external + audioTracks + 1
else:
# No external subtitles added via PlayUtils
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
# Post playback to server
self.logMsg("Sending POST play started.", 1)
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# save data map for updates and position calls
data = {
'runtime': runtime,
'item_id': item_id,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime)
}
self.played_information[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
# log some playback stats
if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1
# reset in progress position
#self.reportPlayback()
def GetPlayStats(self):
return self.playStats
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("onPlayBackEnded", 0)
#workaround when strm files are launched through the addon - mark watched when finished playing
#TODO --> mark watched when 95% is played of the file
WINDOW = xbmcgui.Window( 10000 )
if WINDOW.getProperty("virtualstrm") != "":
try:
id = WINDOW.getProperty("virtualstrm")
type = WINDOW.getProperty("virtualstrmtype")
watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % id
self.doUtils.downloadUrl(watchedurl, postBody="", type="POST")
librarySync.updatePlayCount(id)
except: pass
WINDOW.clearProperty("virtualstrm")
self.stopAll()
def onPlayBackStopped( self ): def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file # Will be called when user stops xbmc playing a file
self.logMsg("onPlayBackStopped", 0) self.logMsg("ONPLAYBACK_STOPPED", 2)
self.stopAll() self.stopAll()
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
self.stopAll()
def stopAll(self):
if not self.played_information:
return
self.logMsg("Played_information: %s" % str(self.played_information), 1)
# Process each items
for item in self.played_information:
data = self.played_information.get(item)
if data:
self.logMsg("Item path: %s" % item, 1)
self.logMsg("Item data: %s" % str(data), 1)
runtime = data.get('runtime')
currentPosition = data.get('currentPosition')
itemId = data.get('item_id')
refresh_id = data.get('refresh_id')
currentFile = data.get('currentfile')
type = data.get('Type')
playMethod = data.get('playmethod')
if currentPosition and runtime:
self.logMsg("RuntimeTicks: %s" % runtime, 1)
percentComplete = (currentPosition * 10000000) / int(runtime)
markPlayedAt = float(utils.settings('markPlayed')) / 100
self.logMsg("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt))
if percentComplete < markPlayedAt:
# Do not mark as watched for Kodi Monitor
utils.window('played_skipWatched', value="true")
self.stopPlayback(data)
offerDelete = False
if type == "Episode" and utils.settings('offerDeleteTV') == "true":
offerDelete = True
elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
offerDelete = True
if percentComplete >= markPlayedAt and offerDelete:
# Item could be stacked, so only offer to delete the main item.
if not self.stackFiles or itemId == utils.window('%sitem_id' % self.stackFiles):
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % data.get('currentfile').split("/")[-1], "on Emby Server?")
if return_value:
# Delete Kodi entry before Emby
listItem = [itemId]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemId, 1)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.played_information.clear()
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
itemId = data.get('item_id')
currentPosition = data.get('currentPosition')
positionTicks = int(currentPosition * 10000000)
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")

View file

@ -35,8 +35,10 @@
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" /> <setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" /> <setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
<setting type="sep" /> <setting type="sep" />
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
<setting id="markPlayed" label="Mark watched at" type="slider" default="90" range="60,5,100" option="percent" visible="true" enable="true" />
<setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" /> <setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" />
<setting id="offerDeleteTV" type="bool" label=" 30115" visible="eq(-1,true)" enable="true" default="false" /> <setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" />
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" /> <setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" />
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" /> <setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" />
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" /> <setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />

View file

@ -140,7 +140,7 @@ class Service():
# Update and report progress # Update and report progress
playTime = xbmc.Player().getTime() playTime = xbmc.Player().getTime()
totalTime = xbmc.Player().getTotalTime() totalTime = xbmc.Player().getTotalTime()
currentFile = xbmc.Player().getPlayingFile() currentFile = player.currentFile
# Update positionticks # Update positionticks
if player.played_information.get(currentFile) is not None: if player.played_information.get(currentFile) is not None: