Redesign Kodi monitor and player

Allows now to have playback initiated by Kodi - especially when using
direct paths
This commit is contained in:
tomkat83 2016-03-16 17:02:22 +01:00
parent 68ea41fe24
commit 7f674acbac
7 changed files with 302 additions and 264 deletions

View file

@ -1981,7 +1981,7 @@ class API():
def getMediaStreams(self):
"""
Returns the media streams
Returns the media streams for metadata purposes
Output: each track contains a dictionaries
{
@ -2220,7 +2220,7 @@ class API():
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
self.logMsg('Found external subs: %s' % externalsubs)
return externalsubs
def CreateListItemFromPlexItem(self, listItem=None):

View file

@ -82,6 +82,7 @@ class Items(object):
Returns True if sync should stop, else False
"""
self.logMsg('Cannot access file: %s' % url, -1)
import xbmcaddon
string = xbmcaddon.Addon().getLocalizedString
resp = xbmcgui.Dialog().yesno(

View file

@ -3,6 +3,7 @@
###############################################################################
import json
from unicodedata import normalize
import xbmc
import xbmcgui
@ -21,7 +22,8 @@ class KodiMonitor(xbmc.Monitor):
def __init__(self):
self.doUtils = downloadutils.DownloadUtils()
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = xbmc.Player()
self.logMsg("Kodi monitor started.", 1)
@ -60,68 +62,25 @@ class KodiMonitor(xbmc.Monitor):
utils.window('emby_logLevel', value=currentLog)
def onNotification(self, sender, method, data):
window = utils.window
doUtils = self.doUtils
if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1)
if data:
data = json.loads(data,'utf-8')
data = json.loads(data, 'utf-8')
if method == "Player.OnPlay":
# Set up report progress for emby playback
item = data.get('item')
try:
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")):
# Set up properties for player
with embydb.GetEmbyDB() as emby_db:
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("No kodiid returned.", 1)
else:
# Tell everyone else what's going on
utils.window('Plex_currently_playing_itemid',
value=itemid)
url = "{server}/library/metadata/%s" % itemid
result = doUtils.downloadUrl(url)
try:
result.attrib
except AttributeError:
self.logMsg('Could not retrieve PMS xml for %s'
% itemid, -1)
return
playurl = None
count = 0
while not playurl and count < 2:
try:
playurl = xbmc.Player().getPlayingFile()
except RuntimeError:
count += 1
xbmc.sleep(200)
else:
listItem = xbmcgui.ListItem()
playback = pbutils.PlaybackUtils(result)
if type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl,
value="DirectStream")
else:
utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listItem)
self.PlayBackStart(data)
elif method == "Player.OnStop":
# Get rid of some values
utils.window('Plex_currently_playing_itemid', clear=True)
window('Plex_currently_playing_itemid', clear=True)
window('emby_customPlaylist', clear=True)
window('emby_customPlaylist.seektime', clear=True)
window('emby_playbackProps', clear=True)
window('suspend_LibraryThread', clear=True)
window('emby_customPlaylist.seektime', clear=True)
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
@ -188,11 +147,96 @@ class KodiMonitor(xbmc.Monitor):
finally:
embycursor.close()'''
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
utils.window('emby_onWake', value="true")
elif method == "Playlist.OnClear":
pass
pass
def PlayBackStart(self, data):
"""
Called whenever a playback is started
"""
log = self.logMsg
window = utils.window
# Try to get a Kodi ID
item = data.get('item')
try:
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
log("Item is invalid for Plex playstate update.", 0)
return
# Get Plex' item id
with embydb.GetEmbyDB() as emby_db:
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
plexid = emby_dbitem[0]
except TypeError:
log("No Plex id returned for kodiid %s" % kodiid, 0)
return
log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1)
# Get currently playing file - can take a while
try:
currentFile = self.xbmcplayer.getPlayingFile()
xbmc.sleep(300)
except:
currentFile = ""
count = 0
while not currentFile:
xbmc.sleep(100)
try:
currentFile = self.xbmcplayer.getPlayingFile()
except:
pass
if count == 20:
log("No current File - Cancelling OnPlayBackStart...", -1)
return
else:
count += 1
currentFile = currentFile.decode('utf-8')
log("Currently playing file is: %s" % currentFile, 1)
# Normalize to string, because we need to use this in WINDOW(key),
# where key can only be string
currentFile = normalize('NFKD', currentFile).encode('ascii', 'ignore')
log('Normalized filename: %s' % currentFile, 1)
# Set some stuff if Kodi initiated playback
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")):
if self.StartDirectPath(plexid, type, currentFile) is False:
log('Could not initiate monitoring; aborting', -1)
return
# Save currentFile for cleanup later and to be able to access refs
window('plex_lastPlayedFiled', value=currentFile)
window('Plex_currently_playing_itemid', value=plexid)
window("emby_%s.itemid" % currentFile, value=plexid)
log('Finish playback startup', 1)
def StartDirectPath(self, plexid, type, currentFile):
"""
Set some additional stuff if playback was initiated by Kodi, not PKC
"""
result = self.doUtils('{server}/library/metadata/%s' % plexid)
try:
result[0].attrib
except:
self.logMsg('Did not receive a valid XML for plexid %s.'
% plexid, -1)
return False
# Setup stuff, because playback was started by Kodi, not PKC
pbutils.PlaybackUtils(result[0]).setProperties(
currentFile, xbmcgui.ListItem())
if type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % currentFile,
value="DirectStream")
else:
utils.window('emby_%s.playmethod' % currentFile,
value="DirectPlay")
self.logMsg('Window properties set for direct paths!', 0)

View file

@ -278,7 +278,7 @@ class PlaybackUtils():
# Only for direct stream
if playmethod in ("DirectStream"):
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
subtitles = self.API.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
@ -288,12 +288,8 @@ class PlaybackUtils():
externalsubs = []
mapping = {}
item = self.item
itemid = item['Id']
try:
mediastreams = item['MediaSources'][0]['MediaStreams']
except (TypeError, KeyError, IndexError):
return
itemid = self.API.getRatingKey()
mediastreams = self.API.getMediaStreams()
kodiindex = 0
for stream in mediastreams:

View file

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#################################################################################################
###############################################################################
import json
from unicodedata import normalize
import xbmc
import xbmcgui
@ -13,7 +14,7 @@ import downloadutils
from urllib import urlencode
#################################################################################################
###############################################################################
@utils.logging
@ -48,7 +49,9 @@ class Player(xbmc.Player):
return self.playStats
def onPlayBackStarted(self):
"""
Window values need to have been set in Kodimonitor.py
"""
log = self.logMsg
window = utils.window
# Will be called when xbmc starts playing a file
@ -66,205 +69,203 @@ class Player(xbmc.Player):
xbmc.sleep(100)
try:
currentFile = xbmcplayer.getPlayingFile()
except: pass
if count == 5: # try 5 times
except:
pass
if count == 20:
log("Cancelling playback report...", 1)
break
else: count += 1
if currentFile:
self.currentFile = currentFile
# Save currentFile for cleanup later
window('plex_lastPlayedFiled', value=currentFile)
# We may need to wait for info to be set in kodi monitor
itemId = window("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
itemId = window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
log("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
window('Plex_currently_playing_itemid', value=itemId)
log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
embyitem = "emby_%s" % currentFile
runtime = window("%s.runtime" % embyitem)
refresh_id = window("%s.refreshid" % embyitem)
playMethod = window("%s.playmethod" % embyitem)
itemType = window("%s.type" % embyitem)
window('emby_skipWatched%s' % itemId, value="true")
log("Playing itemtype is: %s" % itemType, 1)
# Suspend library sync thread while movie is playing
if itemType in ('movie', 'episode'):
log("Suspending library sync while playing", 1)
window('suspend_LibraryThread', value='true')
customseek = window('emby_customPlaylist.seektime')
if (window('emby_customPlaylist') == "true" and customseek):
# Start at, when using custom playlist (play to Kodi from webclient)
log("Seeking to: %s" % customseek, 1)
xbmcplayer.seekTime(int(customseek))
window('emby_customPlaylist.seektime', clear=True)
seekTime = xbmcplayer.getTime()
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata structure to send to Emby server
url = "{server}/:/timeline?"
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
tracks_query = {
count += 1
if not currentFile:
log('Error getting a currently playing file; abort reporting', -1)
return
currentFile = currentFile.decode('utf-8')
# Normalize to string, because we need to use this in WINDOW(key),
# where key can only be string
currentFile = normalize('NFKD', currentFile).encode('ascii', 'ignore')
log('Normalized filename: %s' % currentFile, 1)
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
# Save currentFile for cleanup later and for references
self.currentFile = currentFile
window('plex_lastPlayedFiled', value=currentFile)
# We may need to wait for info to be set in kodi monitor
itemId = window("emby_%s.itemid" % currentFile)
count = 0
while not itemId:
xbmc.sleep(200)
itemId = window("emby_%s.itemid" % currentFile)
# try 20 times or about 10 seconds
if count == 20:
log("Could not find itemId, cancelling playback report...", -1)
return
count += 1
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
try: # Audio tracks
indexAudio = result['currentaudiostream']['index']
except (KeyError, TypeError):
indexAudio = 0
try: # Subtitles tracks
indexSubs = result['currentsubtitle']['index']
except (KeyError, TypeError):
indexSubs = 0
embyitem = "emby_%s" % currentFile
runtime = window("%s.runtime" % embyitem)
refresh_id = window("%s.refreshid" % embyitem)
playMethod = window("%s.playmethod" % embyitem)
itemType = window("%s.type" % embyitem)
window('emby_skipWatched%s' % itemId, value="true")
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
log("Playing itemtype is: %s" % itemType, 1)
# Suspend library sync thread while movie is playing
if itemType in ('movie', 'episode'):
log("Suspending library sync while playing", 1)
window('suspend_LibraryThread', value='true')
# Postdata for the audio
postdata['AudioStreamIndex'] = indexAudio + 1
# Postdata for the subtitles
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = window("%s.indexMapping" % embyitem)
customseek = window('emby_customPlaylist.seektime')
if (window('emby_customPlaylist') == "true" and customseek):
# Start at, when using custom playlist (play to Kodi from webclient)
log("Seeking to: %s" % customseek, 1)
xbmcplayer.seekTime(int(customseek))
window('emby_customPlaylist.seektime', clear=True)
if mapping: # Set in playbackutils.py
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
seekTime = xbmcplayer.getTime()
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
subindex = indexSubs - len(externalIndex) + audioTracks + 1
postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Post playback to server
# log("Sending POST play started: %s." % postdata, 2)
# self.doUtils(url, postBody=postdata, type="POST")
# Ensure we do have a runtime
try:
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
# Postdata structure to send to Emby server
url = "{server}/:/timeline?"
postdata = {
playQueueVersion = utils.window(
'playQueueVersion')
playQueueID = utils.window(
'playQueueID')
playQueueItemID = utils.window(
'plex_%s.playQueueItemID' % currentFile)
# Save data map for updates and position calls
data = {
'playQueueVersion': playQueueVersion,
'playQueueID': playQueueID,
'playQueueItemID': playQueueItemID,
'runtime': runtime * 1000,
'item_id': itemId,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime)
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
self.played_info[currentFile] = data
log("ADDING_FILE: %s" % self.played_info, 1)
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
# log some playback stats
'''if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
try: # Audio tracks
indexAudio = result['currentaudiostream']['index']
except (KeyError, TypeError):
indexAudio = 0
try: # Subtitles tracks
indexSubs = result['currentsubtitle']['index']
except (KeyError, TypeError):
indexSubs = 0
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
# Postdata for the audio
postdata['AudioStreamIndex'] = indexAudio + 1
# Postdata for the subtitles
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = window("%s.indexMapping" % embyitem)
if mapping: # Set in playbackutils.py
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
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'''
# Internal subtitle currently selected
subindex = indexSubs - len(externalIndex) + audioTracks + 1
postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
# Post playback to server
# log("Sending POST play started: %s." % postdata, 2)
# self.doUtils(url, postBody=postdata, type="POST")
# Ensure we do have a runtime
try:
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
playQueueVersion = window('playQueueVersion')
playQueueID = window('playQueueID')
playQueueItemID = window('plex_%s.playQueueItemID' % currentFile)
# Save data map for updates and position calls
data = {
'playQueueVersion': playQueueVersion,
'playQueueID': playQueueID,
'playQueueItemID': playQueueItemID,
'runtime': runtime * 1000,
'item_id': itemId,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime)
}
self.played_info[currentFile] = data
log("ADDING_FILE: %s" % self.played_info, 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):
# Don't use if Plex Companion is enabled
@ -281,7 +282,7 @@ class Player(xbmc.Player):
# only report playback if emby has initiated the playback (item_id has value)
if data:
# Get playback information
# Get playback inforation
itemId = data['item_id']
audioindex = data['AudioStreamIndex']
subtitleindex = data['SubtitleStreamIndex']
@ -451,18 +452,13 @@ class Player(xbmc.Player):
window = utils.window
# Will be called when user stops xbmc playing a file
log("ONPLAYBACK_STOPPED", 2)
window('emby_customPlaylist', clear=True)
window('emby_customPlaylist.seektime', clear=True)
window('emby_playbackProps', clear=True)
window('suspend_LibraryThread', clear=True)
log("Clear playlist properties.", 1)
self.stopAll()
def onPlayBackEnded(self):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
utils.window('emby_customPlaylist.seektime', clear=True)
utils.window('suspend_LibraryThread', clear=True)
self.stopAll()
def stopAll(self):

View file

@ -76,10 +76,10 @@ class SubscriptionManager:
keyid = None
count = 0
while not keyid:
if count > 10:
if count > 300:
break
keyid = WINDOW.getProperty('Plex_currently_playing_itemid')
xbmc.sleep(1000)
xbmc.sleep(100)
count += 1
if keyid:
self.lastkey = "/library/metadata/%s"%keyid

View file

@ -109,6 +109,7 @@ class Service():
# ws = wsc.WebSocket_Client()
library = librarysync.LibrarySync()
kplayer = player.Player()
xplayer = xbmc.Player()
plx = PlexAPI.PlexAPI()
plexCompanion = PlexCompanion.PlexCompanion()
@ -136,11 +137,11 @@ class Service():
if (user.currUser is not None) and user.HasAccess:
# If an item is playing
if xbmc.Player().isPlaying():
if xplayer.isPlaying():
try:
# Update and report progress
playtime = xbmc.Player().getTime()
totalTime = xbmc.Player().getTotalTime()
playtime = xplayer.getTime()
totalTime = xplayer.getTotalTime()
currentFile = kplayer.currentFile
# Update positionticks