20ac3c9465
I grew tired of restarting Kodi everytime....so I made it so the log level changes on the stop without restarting Kodi. Also removed NextUp import and added the downloadUtils 200 response to help debug in the future.
347 lines
13 KiB
Python
347 lines
13 KiB
Python
import xbmcaddon
|
|
import xbmcplugin
|
|
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
|
|
|
|
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()
|
|
|
|
addonName = clientInfo.getAddonName()
|
|
addonId = clientInfo.getAddonId()
|
|
addon = xbmcaddon.Addon(id=addonId)
|
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
|
|
logLevel = 0
|
|
played_information = {}
|
|
settings = None
|
|
playStats = {}
|
|
|
|
def __init__( self, *args ):
|
|
|
|
self.__dict__ = self._shared_state
|
|
self.logMsg("Starting playback monitor service", 1)
|
|
|
|
def logMsg(self, msg, lvl=1):
|
|
|
|
self.className = self.__class__.__name__
|
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
|
|
|
def hasData(self, data):
|
|
if(data == None or len(data) == 0 or data == "None"):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def stopAll(self):
|
|
|
|
if(len(self.played_information) == 0):
|
|
return
|
|
|
|
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
self.logMsg("emby Service -> played_information : " + str(self.played_information))
|
|
|
|
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))
|
|
|
|
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")
|
|
|
|
# Prevent websocket feedback
|
|
self.WINDOW.setProperty("played_itemId", item_id)
|
|
|
|
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
|
|
|
|
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')
|
|
|
|
self.stopPlayback(data)
|
|
|
|
if percentComplete > .80 and data.get("Type") == "Episode" and addonSettings.getSetting("offerDelete")=="true":
|
|
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete\n" + data.get("currentfile").split("/")[-1] + "\non Emby Server? ")
|
|
if return_value:
|
|
url='{server}/mediabrowser/Items/' + item_id
|
|
xbmc.log('Deleting via URL: ' + url)
|
|
self.doUtils.downloadUrl(url, type="DELETE")
|
|
xbmc.sleep (15000)
|
|
xbmc.executebuiltin( "Container.Refresh" )
|
|
#if(refresh_id != None):
|
|
#report updates playcount and resume status to Kodi and MB3
|
|
#librarySync.updatePlayCount(item_id)
|
|
|
|
# Stop transcoding
|
|
if self.WINDOW.getProperty("transcoding%s" % item_id) == "true":
|
|
deviceId = self.clientInfo.getMachineId()
|
|
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
|
|
self.doUtils.downloadUrl(url, type="DELETE")
|
|
self.WINDOW.clearProperty("transcoding%s" % item_id)
|
|
|
|
self.played_information.clear()
|
|
|
|
def stopPlayback(self, data):
|
|
|
|
self.logMsg("stopPlayback called", 2)
|
|
|
|
item_id = data.get("item_id")
|
|
currentPosition = data.get("currentPosition")
|
|
positionTicks = int(currentPosition * 10000000)
|
|
|
|
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
|
|
|
|
postdata = {
|
|
'ItemId': item_id,
|
|
'MediaSourceId': item_id,
|
|
'PositionTicks': positionTicks
|
|
}
|
|
|
|
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
|
|
|
|
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()
|
|
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:
|
|
|
|
# Get playback information
|
|
item_id = data.get("item_id")
|
|
audioindex = data.get("AudioStreamIndex")
|
|
subtitleindex = data.get("SubtitleStreamIndex")
|
|
playTime = data.get("currentPosition")
|
|
playMethod = data.get("playmethod")
|
|
paused = data.get("paused")
|
|
|
|
if paused is None:
|
|
paused = False
|
|
|
|
# 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')
|
|
|
|
postdata = {
|
|
'QueueableMediaTypes': "Video",
|
|
'CanSeek': True,
|
|
'ItemId': item_id,
|
|
'MediaSourceId': item_id,
|
|
'PlayMethod': playMethod,
|
|
'IsPaused': paused,
|
|
'VolumeLevel': volume,
|
|
'IsMuted': muted
|
|
}
|
|
|
|
if playTime:
|
|
postdata['PositionTicks'] = int(playTime * 10000000)
|
|
|
|
if audioindex:
|
|
postdata['AudioStreamIndex'] = audioindex
|
|
|
|
if subtitleindex:
|
|
postdata['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"
|
|
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"
|
|
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 = self.WINDOW
|
|
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")
|
|
audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex")
|
|
subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex")
|
|
playMethod = WINDOW.getProperty(currentFile + "playmethod")
|
|
itemType = WINDOW.getProperty(currentFile + "type")
|
|
seekTime = WINDOW.getProperty(currentFile + "seektime")
|
|
|
|
# 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')
|
|
|
|
if seekTime:
|
|
PlaybackUtils().seekToPosition(int(seekTime))
|
|
else:
|
|
seekTime = 0
|
|
|
|
url = "{server}/mediabrowser/Sessions/Playing"
|
|
postdata = {
|
|
'QueueableMediaTypes': "Video",
|
|
'CanSeek': True,
|
|
'ItemId': item_id,
|
|
'MediaSourceId': item_id,
|
|
'PlayMethod': playMethod,
|
|
'VolumeLevel': volume,
|
|
'PositionTicks': int(seekTime),
|
|
'IsMuted': muted
|
|
}
|
|
|
|
if audioindex:
|
|
postdata['AudioStreamIndex'] = audioindex
|
|
|
|
if subtitleindex:
|
|
postdata['SubtitleStreamIndex'] = subtitleindex
|
|
|
|
# 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': audioindex,
|
|
'SubtitleStreamIndex': subtitleindex,
|
|
'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()
|