mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-25 02:06:09 +00:00
Transcode revision, stack files, playback cleanup
This commit is contained in:
parent
0294957d14
commit
4ab6991968
5 changed files with 726 additions and 853 deletions
|
@ -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
|
||||
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
|
|
@ -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
|
||||
xbmc.Player().play(playlist)'''
|
|
@ -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()
|
||||
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")
|
|
@ -35,8 +35,10 @@
|
|||
<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 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="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="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" />
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue