diff --git a/resources/lib/PlayUtils.py b/resources/lib/PlayUtils.py
index efb48d51..c902298f 100644
--- a/resources/lib/PlayUtils.py
+++ b/resources/lib/PlayUtils.py
@@ -1,30 +1,20 @@
# -*- coding: utf-8 -*-
-#################################################################################################
-# utils class
#################################################################################################
import xbmc
import xbmcgui
-import xbmcaddon
import xbmcvfs
from ClientInformation import ClientInformation
import Utils as utils
-###########################################################################
+#################################################################################################
class PlayUtils():
- _shared_state = {}
-
clientInfo = ClientInformation()
-
addonName = clientInfo.getAddonName()
- WINDOW = xbmcgui.Window(10000)
-
- def __init__(self):
- self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
@@ -33,59 +23,51 @@ class PlayUtils():
def getPlayUrl(self, server, id, result):
- WINDOW = self.WINDOW
- username = WINDOW.getProperty('currUser')
- server = WINDOW.getProperty('server%s' % username)
-
if self.isDirectPlay(result,True):
# Try direct play
playurl = self.directPlay(result)
if playurl:
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):
# Try direct stream
playurl = self.directStream(result, server, id)
if playurl:
self.logMsg("File is direct streaming.", 1)
- WINDOW.setProperty("%splaymethod" % playurl, "DirectStream")
+ utils.window("%splaymethod" % playurl, value="DirectStream")
elif self.isTranscoding(result):
# Try transcoding
playurl = self.transcoding(result, server, id)
if playurl:
self.logMsg("File is transcoding.", 1)
- WINDOW.setProperty("%splaymethod" % playurl, "Transcode")
- else:
- # Error
- return False
+ utils.window("%splaymethod" % playurl, value="Transcode")
+
+ else: # Error
+ utils.window("playurlFalse", value="true")
+ return
return playurl.encode('utf-8')
- def isDirectPlay(self, result, dialog=False):
+
+ def isDirectPlay(self, result, dialog = False):
# Requirements for Direct play:
# FileSystem, Accessible path
-
- playhttp = utils.settings('playFromStream')
- # User forcing to play via HTTP instead of SMB
- if playhttp == "true":
+ if utils.settings('playFromStream') == "true":
+ # User forcing to play via HTTP instead of SMB
self.logMsg("Can't direct play: Play from HTTP is enabled.", 1)
return False
- canDirectPlay = result[u'MediaSources'][0][u'SupportsDirectPlay']
+ canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
# Make sure it's supported by server
if not canDirectPlay:
self.logMsg("Can't direct play: Server does not allow or support it.", 1)
return False
- if result['Path'].endswith('.strm'):
- # Allow strm loading when direct playing
- return True
-
- location = result[u'LocationType']
+ location = result['LocationType']
# File needs to be "FileSystem"
- if u'FileSystem' in location:
+ if 'FileSystem' in location:
# Verify if path is accessible
if self.fileExists(result):
return True
@@ -107,53 +89,49 @@ class PlayUtils():
return False
-
def directPlay(self, result):
try:
- try:
- playurl = result[u'MediaSources'][0][u'Path']
- except:
- 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
+ playurl = result['MediaSources'][0]['Path']
+ except KeyError:
+ playurl = result['Path']
- # Network - SMB protocol
- if "\\\\" in playurl:
- 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
+ if 'VideoType' in result:
+ # Specific format modification
+ if 'Dvd' in result['VideoType']:
+ playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
+ elif 'BluRay' in result['VideoType']:
+ playurl = "%s/BDMV/index.bdmv" % playurl
+
+ # Network - SMB protocol
+ if "\\\\" in playurl:
+ 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):
# Requirements for Direct stream:
# 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
if not canDirectStream:
return False
- location = result[u'LocationType']
+ location = result['LocationType']
# 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)
return False
@@ -171,33 +149,29 @@ class PlayUtils():
playurl = self.directPlay(result)
return playurl
- try:
- if "ThemeVideo" in type:
- playurl ="%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
+ if "ThemeVideo" in type:
+ playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
+
+ elif "Video" in type:
+ playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
+
+ elif "Audio" in type:
+ playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
+
+ return playurl
- elif "Video" in type:
- playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
-
- elif "Audio" in type:
- playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
-
- 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']
+ canTranscode = result['MediaSources'][0]['SupportsTranscoding']
# Make sure it's supported by server
if not canTranscode:
return False
- location = result[u'LocationType']
+ location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
- if u'Virtual' in location:
+ if 'Virtual' in location:
return False
return True
@@ -208,31 +182,28 @@ class PlayUtils():
# Allow strm loading when transcoding
playurl = self.directPlay(result)
return playurl
-
- try:
- # Play transcoding
- deviceId = self.clientInfo.getMachineId()
- playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
- playurl = "%s&VideoCodec=h264&AudioCodec=ac3&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
- self.logMsg("Playurl: %s" % playurl)
-
- return playurl
- except:
- self.logMsg("Transcoding failed.")
- return False
+ # Play transcoding
+ deviceId = self.clientInfo.getMachineId()
+ 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)
- # Works out if the network quality can play directly or if transcoding is needed
+ playurl = self.audioSubsPref(playurl, result.get('MediaSources'))
+ self.logMsg("Playurl: %s" % playurl, 1)
+
+ return playurl
+
+
def isNetworkQualitySufficient(self, result):
-
+ # Works out if the network quality can play directly or if transcoding is needed
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = settingsVideoBitRate * 1000
try:
- mediaSources = result[u'MediaSources']
- sourceBitRate = int(mediaSources[0][u'Bitrate'])
- except:
- self.logMsg("Bitrate value is missing.")
+ mediaSources = result['MediaSources']
+ sourceBitRate = int(mediaSources[0]['Bitrate'])
+ except KeyError:
+ self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1)
if settingsVideoBitRate < sourceBitRate:
@@ -243,58 +214,40 @@ class PlayUtils():
def getVideoBitRate(self):
# get the addon video quality
videoQuality = utils.settings('videoBitRate')
+ bitrate = {
- if (videoQuality == "0"):
- return 664
- elif (videoQuality == "1"):
- return 996
- elif (videoQuality == "2"):
- return 1320
- elif (videoQuality == "3"):
- return 2000
- elif (videoQuality == "4"):
- return 3200
- elif (videoQuality == "5"):
- return 4700
- elif (videoQuality == "6"):
- return 6200
- elif (videoQuality == "7"):
- return 7700
- elif (videoQuality == "8"):
- return 9200
- elif (videoQuality == "9"):
- return 10700
- elif (videoQuality == "10"):
- return 12200
- elif (videoQuality == "11"):
- 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)
+ '0': 664,
+ '1': 996,
+ '2': 1320,
+ '3': 2000,
+ '4': 3200,
+ '5': 4700,
+ '6': 6200,
+ '7': 7700,
+ '8': 9200,
+ '9': 10700,
+ '10': 12200,
+ '11': 13700,
+ '12': 15200,
+ '13': 16700,
+ '14': 18200,
+ '15': 20000,
+ '16': 40000,
+ '17': 100000,
+ '18': 1000000
+ }
+
+ # max bit rate supported by server (max signed 32bit integer)
+ return bitrate.get(videoQuality, 2147483)
def fileExists(self, result):
- if u'Path' not in result:
+ if 'Path' not in result:
# File has no path in server
return False
# Convert Emby path to a path we can verify
path = self.directPlay(result)
- if not path:
- return False
try:
pathexists = xbmcvfs.exists(path)
@@ -312,4 +265,87 @@ class PlayUtils():
return True
else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
- return False
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py
index b01bbe17..6a7e2f2b 100644
--- a/resources/lib/PlaybackUtils.py
+++ b/resources/lib/PlaybackUtils.py
@@ -1,190 +1,151 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
-import xbmc
-import xbmcplugin
-import xbmcgui
-import xbmcaddon
-import urllib
import datetime
-import time
import json as json
-import inspect
import sys
+import xbmc
+import xbmcaddon
+import xbmcplugin
+import xbmcgui
+
+from API import API
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
-from ReadKodiDB import ReadKodiDB
-from ReadEmbyDB import ReadEmbyDB
+from ClientInformation import ClientInformation
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():
- settings = None
+ clientInfo = ClientInformation()
+ doUtils = DownloadUtils()
+ api = API()
+
+ addon = xbmcaddon.Addon()
language = addon.getLocalizedString
- logLevel = 0
- downloadUtils = DownloadUtils()
+ addonName = clientInfo.getAddonName()
- WINDOW.clearProperty('playurlFalse')
-
- def __init__(self, *args):
- pass
+ username = utils.window('currUser')
+ userid = utils.window('userId%s' % username)
+ server = utils.window('server%s' % username)
- def PLAY(self, result, setup="service"):
- xbmc.log("PLAY Called")
- WINDOW = xbmcgui.Window(10000)
-
- username = WINDOW.getProperty('currUser')
- userid = WINDOW.getProperty('userId%s' % username)
- server = WINDOW.getProperty('server%s' % username)
+ def logMsg(self, msg, lvl=1):
- try:
- id = result["Id"]
- except:
- return
+ className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
- userData = result['UserData']
+ def PLAY(self, result, setup = "service"):
- # BOOKMARK - RESUME POINT
- timeInfo = API().getTimeInfo(result)
- jumpBackSec = int(utils.settings("resumeJumpBack"))
+ self.logMsg("PLAY Called", 1)
+
+ api = self.api
+ doUtils = self.doUtils
+ server = self.server
+
+ listItem = xbmcgui.ListItem()
+ id = result['Id']
+ 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)
if seekTime > jumpBackSec:
# To avoid negative bookmark
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
- 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
- # We can add the option right here
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
- intros = self.downloadUtils.downloadUrl(url)
- if intros[u'TotalRecordCount'] == 0:
- pass
- else:
- for intro in intros[u'Items']:
- introId = intro[u'Id']
- itemsToPlay.append(introId)
+ intros = doUtils.downloadUrl(url)
+ if intros['TotalRecordCount'] != 0:
+ for intro in intros['Items']:
+ introPlayurl = PlayUtils().getPlayUrl(server, intro['Id'], intro)
+ self.setProperties(introPlayurl, intro)
+ playstack.append(introPlayurl)
# Add original item
- itemsToPlay.append(id)
+ playstack.append(playurl)
+ self.setProperties(playurl, result)
- # For split movies
- if u'PartCount' in result:
- partcount = result[u'PartCount']
- # Get additional parts/playurl
+ mainArt = API().getArtwork(result, "Primary")
+ listItem.setThumbnailImage(mainArt)
+ listItem.setIconImage(mainArt)
+
+ # Get additional parts/playurl
+ if result.get('PartCount'):
url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
- parts = self.downloadUtils.downloadUrl(url)
- for part in parts[u'Items']:
- partId = part[u'Id']
- itemsToPlay.append(partId)
-
- 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
+ parts = doUtils.downloadUrl(url)
+ for part in parts['Items']:
+ additionalPlayurl = PlayUtils().getPlayUrl(server, part['Id'], part)
+ self.setProperties(additionalPlayurl, part)
+ playstack.append(additionalPlayurl)
- 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 playurl.endswith(".strm"):
- 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 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 WINDOW.getProperty("%splaymethod" % playurl) != "Transcode":
+ self.logMsg("Returned playurl: %s" % playurl, 1)
+ listItem.setPath(playurl)
+
+ if utils.window("%splaymethod" % playurl) != "Transcode":
# Only for direct play and direct 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)
- #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)
- positionurl = "%s/mediabrowser/Users/%s/PlayingItems/%s" % (server, userid, id)
- deleteurl = "%s/mediabrowser/Items/%s" % (server, id)
-
- # 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":
+ # Launch the playback - only set the listitem props if we're not using the setresolvedurl approach
+ if setup == "service" or xbmc.getCondVisibility('Window.IsActive(home)'):
+ # Sent via websocketclient.py or default.py but via widgets
self.setListItemProps(server, id, listItem, result)
- xbmc.Player().play(playurl,listItem)
+ xbmc.Player().play(playurl, listItem)
elif setup == "default":
- if xbmc.getCondVisibility("Window.IsActive(home)"):
- self.setListItemProps(server, id, listItem, result)
- xbmc.Player().play(playurl,listItem)
- else:
- xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
+ # Sent via default.py
+ xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
def externalSubs(self, id, playurl, server, mediaSources):
@@ -214,173 +175,170 @@ class PlaybackUtils():
kodiindex += 1
mapping = json.dumps(mapping)
- utils.window('%sIndexMapping' % playurl, mapping)
+ utils.window('%sIndexMapping' % playurl, value=mapping)
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)
- # Present the list of audio to select from
- audioStreamsList = {}
- audioStreams = []
- selectAudioIndex = ""
- subtitleStreamsList = {}
- subtitleStreams = ['No subtitles']
- selectSubsIndex = ""
- playurlprefs = "%s" % url
+ utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
+ utils.window("%stype" % playurl, value=type)
+ utils.window("%sitem_id" % playurl, value=id)
- mediaStream = mediaSources[0].get('MediaStreams')
- for stream in mediaStream:
- index = stream['Index']
- # Since Emby returns all possible tracks together, have to sort them.
- 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)
+ if type == "Episode":
+ utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
+ else:
+ utils.window("%srefresh_id" % playurl, value=id)
- elif 'Subtitle' in stream['Type']:
- try:
- track = stream['Language']
- subtitleStreamsList[track] = index
- subtitleStreams.append(track)
- except:
- track = stream['Codec']
- 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]
- 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
+ def setArt(self, list, name, path):
- # 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':
+ if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
list.setProperty(name, path)
else:
list.setArt({name:path})
+
return list
def setListItemProps(self, server, id, listItem, result):
- # set up item and item info
- thumbID = id
- eppNum = -1
- seasonNum = -1
- tvshowTitle = ""
-
- if(result.get("Type") == "Episode"):
- thumbID = result.get("SeriesId")
- seasonNum = result.get("ParentIndexNumber")
- eppNum = result.get("IndexNumber")
- tvshowTitle = result.get("SeriesName")
+ # Set up item and item info
+ api = self.api
+
+ type = result.get('Type')
+ people = api.getPeople(result)
+ studios = api.getStudios(result)
+
+ 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,'tvshow.poster', API().getArtwork(result, "SeriesPrimary"))
self.setArt(listItem,'clearart', API().getArtwork(result, "Art"))
- self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art"))
+ self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art"))
self.setArt(listItem,'clearlogo', API().getArtwork(result, "Logo"))
- self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo"))
- self.setArt(listItem,'discart', API().getArtwork(result, "Disc"))
+ self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo"))
+ self.setArt(listItem,'discart', API().getArtwork(result, "Disc"))
self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop"))
- 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)})
+ self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
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
while not xbmc.Player().isPlaying():
- count = count + 1
+ count += 1
if count >= 10:
return
else:
xbmc.sleep(500)
- #Jump to resume point
- jumpBackSec = int(addon.getSetting("resumeJumpBack"))
- seekToTime = seekTo - jumpBackSec
+ # Jump to seek position
count = 0
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
- count = count + 1
- #xbmc.Player().pause
- #xbmc.sleep(100)
- xbmc.Player().seekTime(seekToTime)
+ count += 1
+ xbmc.Player().seekTime(seekTo)
xbmc.sleep(100)
- #xbmc.Player().play()
def PLAYAllItems(self, items, startPositionTicks):
- utils.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
- utils.logMsg("PlayBackUtils", "Items : " + str(items))
+
+ self.logMsg("== ENTER: PLAYAllItems ==")
+ self.logMsg("Items: %s" % items)
+
+ doUtils = self.doUtils
+
+ playlist = xbmc.Playlist(xbmc.PLAYLIST_VIDEO)
+ playlist.clear()
+ started = False
+
+ 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)
+
+ addition = self.addPlaylistItem(playlist, result)
+ if not started and addition:
+ started = True
+ self.logMsg("Starting Playback Pre", 1)
+ xbmc.Player().play(playlist)
+
+ if not started:
+ self.logMsg("Starting Playback Post", 1)
+ xbmc.Player().play(playlist)
+
+ # Seek to position
+ if startPositionTicks:
+ seekTime = startPositionTicks / 10000000.0
+ self.seekToPosition(seekTime)
+
+ 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)
username = WINDOW.getProperty('currUser')
@@ -389,42 +347,6 @@ class PlaybackUtils():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
- started = False
-
- for itemID in items:
-
- 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
- added = self.addPlaylistItem(playlist, item_data, server, userid)
- if(added and started == False):
- started = True
- utils.logMsg("PlayBackUtils", "Starting Playback Pre")
- xbmc.Player().play(playlist)
-
- if(started == False):
- utils.logMsg("PlayBackUtils", "Starting Playback Post")
- xbmc.Player().play(playlist)
-
- #seek to position
- seekTime = 0
- if(startPositionTicks != None):
- seekTime = (startPositionTicks / 1000) / 10000
-
- if seekTime > 0:
- self.seekToPosition(seekTime)
-
- def PLAYAllEpisodes(self, items):
- 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 item in items:
@@ -434,80 +356,4 @@ class PlaybackUtils():
item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid)
- 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
\ No newline at end of file
+ xbmc.Player().play(playlist)'''
\ No newline at end of file
diff --git a/resources/lib/Player.py b/resources/lib/Player.py
index 985a4f95..2ce90c09 100644
--- a/resources/lib/Player.py
+++ b/resources/lib/Player.py
@@ -1,165 +1,229 @@
-import xbmcaddon
-import xbmcplugin
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json as json
+
import xbmc
import xbmcgui
-import os
-import threading
-import json
-import inspect
-
-import KodiMonitor
-import Utils as utils
from DownloadUtils import DownloadUtils
from WebSocketClient import WebSocketThread
-from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
from LibrarySync import LibrarySync
-from PlaybackUtils import PlaybackUtils
-from ReadEmbyDB import ReadEmbyDB
-from API import API
+import Utils as utils
-librarySync = LibrarySync()
+#################################################################################################
-# service class for playback monitoring
class Player( xbmc.Player ):
# Borg - multiple instances, shared state
_shared_state = {}
-
+
xbmcplayer = xbmc.Player()
doUtils = DownloadUtils()
clientInfo = ClientInformation()
ws = WebSocketThread()
+ librarySync = LibrarySync()
addonName = clientInfo.getAddonName()
- WINDOW = xbmcgui.Window(10000)
-
- logLevel = 0
played_information = {}
- settings = None
playStats = {}
+ currentFile = None
+ stackFiles = None
+ stackElapsed = 0
+
+ def __init__(self, *args):
- audioPref = "default"
- subsPref = "default"
-
- def __init__( self, *args ):
-
self.__dict__ = self._shared_state
- self.logMsg("Starting playback monitor service", 1)
-
+ self.logMsg("Starting playback monitor.", 2)
+
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
- def setAudioSubsPref(self, audio, subs):
- self.audioPref = audio
- self.subsPref = subs
-
- def hasData(self, data):
- if(data == None or len(data) == 0 or data == "None"):
- return False
+ def GetPlayStats(self):
+ return self.playStats
+
+ def currentStackItem(self, stackItems):
+ # Only for stacked items - stack://
+ 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:
- return True
-
- def stopAll(self):
+ self.stackFiles = None
+ self.currentFile = currentFile
+ self.stackElapsed = 0
- WINDOW = xbmcgui.Window(10000)
-
- if(len(self.played_information) == 0):
- return
+ self.logMsg("ONPLAYBACK_STARTED: %s" % currentFile, 0)
+
+ # We may need to wait for info to be set in kodi monitor
+ itemId = utils.window("%sitem_id" % currentFile)
+ tryCount = 0
+ while not itemId:
- self.logMsg("emby Service -> played_information : " + str(self.played_information))
+ xbmc.sleep(500)
+ itemId = utils.window("%sitem_id" % currentFile)
+ if tryCount == 20: # try 20 times or about 10 seconds
+ break
+ else: tryCount += 1
+
+ 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:
- 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))
+ self.logMsg("Mapping for subtitles index: %s" % mapping, 2)
- runtime = data.get("runtime")
- currentPosition = data.get("currentPosition")
- item_id = data.get("item_id")
- refresh_id = data.get("refresh_id")
- currentFile = data.get("currentfile")
- type = data.get("Type")
- playMethod = data.get('playmethod')
+ # 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('result').get('volume')
+ muted = result.get('result').get('muted')
- # Prevent websocket feedback
- self.WINDOW.setProperty("played_itemId", item_id)
+ url = "{server}/mediabrowser/Sessions/Playing"
+ postdata = {
- if(currentPosition != None and self.hasData(runtime)):
- runtimeTicks = int(runtime)
- self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks))
- percentComplete = (currentPosition * 10000000) / runtimeTicks
- markPlayedAt = float(90) / 100
+ 'QueueableMediaTypes': "Video",
+ 'CanSeek': True,
+ 'ItemId': itemId,
+ 'MediaSourceId': itemId,
+ '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))
- if percentComplete < markPlayedAt:
- # Do not mark as watched
- self.WINDOW.setProperty('played_skipWatched', 'true')
+ # Get the current audio track and subtitles
+ if playMethod == "Transcode":
+ # property set in PlayUtils.py
+ postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
+ postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile)
- self.stopPlayback(data)
-
- offerDelete=False
- if data.get("Type") == "Episode" and utils.settings("offerDeleteTV")=="true":
- offerDelete = True
- elif data.get("Type") == "Movie" and utils.settings("offerDeleteMovies")=="true":
- offerDelete = True
-
- if percentComplete > .80 and offerDelete == True:
- return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete\n" + data.get("currentfile").split("/")[-1] + "\non Emby Server? ")
- if return_value:
- # Delete Kodi entry before Emby
- listItem = [item_id]
- LibrarySync().removefromDB(listItem, True)
-
- # Stop transcoding
- 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")
+ 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)
- self.played_information.clear()
-
- def stopPlayback(self, data):
-
- self.logMsg("stopPlayback called", 2)
-
- item_id = data.get("item_id")
- currentPosition = data.get("currentPosition")
- positionTicks = int(currentPosition * 10000000)
+ # 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 = ""
- url = "{server}/mediabrowser/Sessions/Playing/Stopped"
-
- postdata = {
- 'ItemId': item_id,
- 'MediaSourceId': item_id,
- 'PositionTicks': positionTicks
- }
+ 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.doUtils.downloadUrl(url, postBody=postdata, type="POST")
-
+ # 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': itemId,
+ 'refresh_id': refresh_id,
+ 'currentfile': currentFile,
+ 'AudioStreamIndex': postdata['AudioStreamIndex'],
+ 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
+ 'playmethod': playMethod,
+ 'Type': itemType,
+ 'currentPosition': int(seekTime) - int(self.stackElapsed)
+ }
+
+ 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'''
+
def reportPlayback(self):
self.logMsg("reportPlayback Called", 2)
xbmcplayer = self.xbmcplayer
-
- if not xbmcplayer.isPlaying():
- self.logMsg("reportPlayback: Not playing anything so returning", 0)
- return
- currentFile = xbmcplayer.getPlayingFile()
+ # Get current file
+ currentFile = self.currentFile
data = self.played_information.get(currentFile)
# 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
- item_id = data.get("item_id")
+ itemId = data.get("item_id")
audioindex = data.get("AudioStreamIndex")
subtitleindex = data.get("SubtitleStreamIndex")
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}'
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')
+ volume = result.get('result').get('volume')
+ muted = result.get('result').get('muted')
postdata = {
+
'QueueableMediaTypes': "Video",
'CanSeek': True,
- 'ItemId': item_id,
- 'MediaSourceId': item_id,
+ 'ItemId': itemId,
+ 'MediaSourceId': itemId,
'PlayMethod': playMethod,
'IsPaused': paused,
'VolumeLevel': volume,
@@ -188,9 +253,14 @@ class Player( xbmc.Player ):
}
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
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
@@ -249,208 +319,127 @@ class Player( xbmc.Player ):
postdata['SubtitleStreamIndex'] = indexSubs
data['SubtitleStreamIndex'] = indexSubs
- else:
- data['AudioStreamIndex'] = audioindex
- data['SubtitleStreamIndex'] = subtitleindex
-
postdata = json.dumps(postdata)
self.logMsg("Report: %s" % postdata, 2)
self.ws.sendProgressUpdate(postdata)
-
+
def onPlayBackPaused( self ):
- currentFile = xbmc.Player().getPlayingFile()
- self.logMsg("PLAYBACK_PAUSED : " + currentFile,2)
- if(self.played_information.get(currentFile) != None):
- self.played_information[currentFile]["paused"] = "true"
+
+ currentFile = self.currentFile
+ self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
+
+ if self.played_information.get(currentFile):
+ self.played_information[currentFile]['paused'] = "true"
+
self.reportPlayback()
-
+
def onPlayBackResumed( self ):
- currentFile = xbmc.Player().getPlayingFile()
- self.logMsg("PLAYBACK_RESUMED : " + currentFile,2)
- if(self.played_information.get(currentFile) != None):
- self.played_information[currentFile]["paused"] = "false"
+
+ currentFile = self.currentFile
+ self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
+
+ if self.played_information.get(currentFile):
+ self.played_information[currentFile]['paused'] = "false"
+
+ self.reportPlayback()
+
+ def onPlayBackSeek( self, time, seekOffset ):
+
+ self.logMsg("PLAYBACK_SEEK", 2)
+ xbmcplayer = self.xbmcplayer
+ # Make position when seeking a bit more accurate
+ position = xbmcplayer.getTime()
+ currentFile = self.currentFile
+
+ if self.played_information.get(currentFile):
+ self.played_information[currentFile]['currentPosition'] = position
+
self.reportPlayback()
- 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 ):
- # Will be called when xbmc starts playing a file
- WINDOW = xbmcgui.Window(10000)
- xbmcplayer = self.xbmcplayer
- self.stopAll()
-
- if xbmcplayer.isPlaying():
-
- currentFile = ""
- 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 ):
# Will be called when user stops xbmc playing a file
- self.logMsg("onPlayBackStopped", 0)
- self.stopAll()
\ No newline at end of file
+ self.logMsg("ONPLAYBACK_STOPPED", 2)
+ 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")
\ No newline at end of file
diff --git a/resources/settings.xml b/resources/settings.xml
index dfb0cd73..1fb1ba6d 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -35,8 +35,10 @@
+
+
-
+
diff --git a/service.py b/service.py
index 76b98270..cf6834f1 100644
--- a/service.py
+++ b/service.py
@@ -140,7 +140,7 @@ class Service():
# Update and report progress
playTime = xbmc.Player().getTime()
totalTime = xbmc.Player().getTotalTime()
- currentFile = xbmc.Player().getPlayingFile()
+ currentFile = player.currentFile
# Update positionticks
if player.played_information.get(currentFile) is not None: