Revamp playback start, part 4

This commit is contained in:
tomkat83 2018-01-21 18:31:49 +01:00
parent 7ecaa376a2
commit 2791da9f65
6 changed files with 148 additions and 342 deletions

View file

@ -1268,6 +1268,15 @@ class API():
res = '2000-01-01 10:00:00'
return res
def getViewCount(self):
"""
Returns the play count for the item as an int or the int 0 if not found
"""
try:
return int(self.item.attrib['viewCount'])
except (KeyError, ValueError):
return 0
def getUserData(self):
"""
Returns a dict with None if a value is missing

View file

@ -45,6 +45,7 @@ STATE_SETTINGS = {
'enableMusic': 'ENABLE_MUSIC',
'enableBackgroundSync': 'BACKGROUND_SYNC'
}
###############################################################################
@ -55,6 +56,8 @@ class KodiMonitor(Monitor):
def __init__(self):
self.xbmcplayer = Player()
Monitor.__init__(self)
for playerid in state.PLAYER_STATES:
state.PLAYER_STATES[playerid] = dict(state.PLAYSTATE)
LOG.info("Kodi monitor started.")
def onScanStarted(self, library):
@ -315,6 +318,8 @@ class KodiMonitor(Monitor):
LOG.info('Aborting playback report - item invalid for updates %s',
data)
return
# Remember that this player has been active
state.ACTIVE_PLAYERS.append(playerid)
playqueue = PQ.PLAYQUEUES[playerid]
info = js.get_player_props(playerid)
json_item = js.get_item(playerid)
@ -356,13 +361,15 @@ class KodiMonitor(Monitor):
container_key = '/library/metadata/%s' % plex_id
state.PLAYER_STATES[playerid]['container_key'] = container_key
LOG.debug('Set the Plex container_key to: %s', container_key)
state.PLAYER_STATES[playerid].update(info)
state.PLAYER_STATES[playerid]['file'] = path
state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
state.PLAYER_STATES[playerid]['plex_id'] = plex_id
state.PLAYER_STATES[playerid]['plex_type'] = plex_type
status = state.PLAYER_STATES[playerid]
status.update(info)
status['file'] = path
status['kodi_id'] = kodi_id
status['kodi_type'] = kodi_type
status['plex_id'] = plex_id
status['plex_type'] = plex_type
status['playmethod'] = item.playmethod
status['playcount'] = item.playcount
LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
def StartDirectPath(self, plex_id, type, currentFile):

View file

@ -138,6 +138,7 @@ def _prep_playlist_stack(xml):
# We will never store clips (trailers) in the Kodi DB
kodi_id = None
kodi_type = None
resume, _ = api.getRuntime()
for part, _ in enumerate(item[0]):
api.setPartNumber(part)
if kodi_id is None:
@ -161,7 +162,9 @@ def _prep_playlist_stack(xml):
'file': path,
'xml_video_element': item,
'listitem': listitem,
'part': part
'part': part,
'playcount': api.getViewCount(),
'offset': resume
})
return stack
@ -188,6 +191,8 @@ def _process_stack(playqueue, stack):
kodi_id=item['kodi_id'],
kodi_type=item['kodi_type'],
xml_video_element=item['xml_video_element'])
playlist_item.playcount = item['playcount']
playlist_item.offset = item['offset']
playlist_item.part = item['part']
playlist_item.init_done = True
pos += 1

View file

@ -2,15 +2,14 @@
###############################################################################
from logging import getLogger
from json import loads
from xbmc import Player, sleep
from xbmc import Player
from utils import window, DateToKodi, getUnixTimestamp, tryDecode, tryEncode
import downloadutils
from utils import window, DateToKodi, getUnixTimestamp, kodi_time_to_millis
from downloadutils import DownloadUtils as DU
import plexdb_functions as plexdb
import kodidb_functions as kodidb
import json_rpc as js
from plexbmchelper.subscribers import LOCKER
import variables as v
import state
@ -22,326 +21,104 @@ LOG = getLogger("PLEX." + __name__)
class PKC_Player(Player):
played_info = state.PLAYED_INFO
playStats = state.PLAYER_STATES
currentFile = None
def __init__(self):
self.doUtils = downloadutils.DownloadUtils
Player.__init__(self)
LOG.info("Started playback monitor.")
def onPlayBackStarted(self):
"""
Will be called when xbmc starts playing a file.
Window values need to have been set in Kodimonitor.py
"""
return
self.stopAll()
# Get current file (in utf-8!)
try:
currentFile = tryDecode(self.getPlayingFile())
sleep(300)
except:
currentFile = ""
count = 0
while not currentFile:
sleep(100)
try:
currentFile = tryDecode(self.getPlayingFile())
except:
pass
if count == 20:
break
else:
count += 1
if not currentFile:
LOG.warn('Error getting currently playing file; abort reporting')
return
# 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("plex_%s.itemid" % tryEncode(currentFile))
count = 0
while not itemId:
sleep(200)
itemId = window("plex_%s.itemid" % tryEncode(currentFile))
if count == 5:
LOG.warn("Could not find itemId, cancelling playback report!")
return
count += 1
LOG.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId))
plexitem = "plex_%s" % tryEncode(currentFile)
runtime = window("%s.runtime" % plexitem)
refresh_id = window("%s.refreshid" % plexitem)
playMethod = window("%s.playmethod" % plexitem)
itemType = window("%s.type" % plexitem)
try:
playcount = int(window("%s.playcount" % plexitem))
except ValueError:
playcount = 0
window('plex_skipWatched%s' % itemId, value="true")
LOG.debug("Playing itemtype is: %s" % itemType)
customseek = window('plex_customplaylist.seektime')
if customseek:
# Start at, when using custom playlist (play to Kodi from
# webclient)
LOG.info("Seeking to: %s" % customseek)
try:
self.seekTime(int(customseek))
except:
LOG.error('Could not seek!')
window('plex_customplaylist.seektime', clear=True)
try:
seekTime = self.getTime()
except RuntimeError:
LOG.error('Could not get current seektime from xbmc player')
seekTime = 0
volume = js.get_volume()
muted = js.get_muted()
# Postdata structure to send to plex 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"
% tryEncode(currentFile))
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex"
% tryEncode(currentFile))
else:
# Get the current kodi audio and subtitles and convert to plex equivalent
indexAudio = js.current_audiostream(1).get('index', 0)
subsEnabled = js.subtitle_enabled(1)
if subsEnabled:
indexSubs = js.current_subtitle(1).get('index', 0)
else:
indexSubs = 0
# Postdata for the audio
postdata['AudioStreamIndex'] = indexAudio + 1
# Postdata for the subtitles
if subsEnabled and len(Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get plex Index
audioTracks = len(Player().getAvailableAudioStreams())
mapping = window("%s.indexMapping" % plexitem)
if mapping: # Set in playbackutils.py
LOG.debug("Mapping for external subtitles index: %s"
% mapping)
externalIndex = loads(mapping)
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'] = ""
# 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:
try:
runtime = self.getTotalTime()
LOG.error("Runtime is missing, Kodi runtime: %s" % runtime)
except:
LOG.error('Could not get kodi runtime, setting to zero')
runtime = 0
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(itemId)
try:
fileid = plex_dbitem[1]
except TypeError:
LOG.info("Could not find fileid in plex db.")
fileid = None
# 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),
'fileid': fileid,
'itemType': itemType,
'playcount': playcount
}
self.played_info[currentFile] = data
LOG.info("ADDING_FILE: %s" % data)
# 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'''
pass
def onPlayBackPaused(self):
currentFile = self.currentFile
LOG.info("PLAYBACK_PAUSED: %s" % currentFile)
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = True
"""
Will be called when playback is paused
"""
pass
def onPlayBackResumed(self):
currentFile = self.currentFile
LOG.info("PLAYBACK_RESUMED: %s" % currentFile)
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = False
"""
Will be called when playback is resumed
"""
pass
def onPlayBackSeek(self, time, seekOffset):
# Make position when seeking a bit more accurate
currentFile = self.currentFile
LOG.info("PLAYBACK_SEEK: %s" % currentFile)
if self.played_info.get(currentFile):
try:
position = self.getTime()
except RuntimeError:
# When Kodi is not playing
return
self.played_info[currentFile]['currentPosition'] = position
"""
Will be called when user seeks to a certain time during playback
"""
pass
def onPlayBackStopped(self):
# Will be called when user stops xbmc playing a file
"""
Will be called when playback is stopped by the user
"""
LOG.info("ONPLAYBACK_STOPPED")
self.cleanup_playback()
self.stopAll()
def onPlayBackEnded(self):
"""
Will be called when playback ends due to the media file being finished
"""
LOG.info("ONPLAYBACK_ENDED")
self.cleanup_playback()
@LOCKER.lockthis
def cleanup_playback(self):
"""
PKC cleanup after playback ends/is stopped
"""
# We might have saved a transient token from a user flinging media via
# Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None
for item in ('plex_currently_playing_itemid',
'plex_customplaylist',
'plex_customplaylist.seektime',
'plex_forcetranscode'):
window(item, clear=True)
# We might have saved a transient token from a user flinging media via
# Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None
LOG.debug("Cleared playlist properties.")
def onPlayBackEnded(self):
# Will be called when xbmc stops playing a file, because the file ended
LOG.info("ONPLAYBACK_ENDED")
self.onPlayBackStopped()
def stopAll(self):
if not self.played_info:
return
LOG.info("Played_information: %s" % self.played_info)
# Process each items
for item in self.played_info:
data = self.played_info.get(item)
if not data:
for playerid in state.ACTIVE_PLAYERS:
status = state.PLAYER_STATES[playerid]
# Check whether we need to mark an item as completely watched
if not status['kodi_id'] or not status['plex_id']:
LOG.info('No PKC info safed for the element just played by Kodi'
' player %s', playerid)
continue
LOG.debug("Item path: %s" % item)
LOG.debug("Item data: %s" % data)
runtime = data['runtime']
currentPosition = data['currentPosition']
itemid = data['item_id']
refresh_id = data['refresh_id']
currentFile = data['currentfile']
media_type = data['Type']
playMethod = data['playmethod']
# Prevent manually mark as watched in Kodi monitor
window('plex_skipWatched%s' % itemid, value="true")
if not currentPosition or not runtime:
# Stop transcoding
if status['playmethod'] == 'Transcode':
LOG.info('Tell the PMS to stop transcoding')
DU().downloadUrl(
'{server}/video/:/transcode/universal/stop',
parameters={'session': v.PKC_MACHINE_IDENTIFIER})
if status['plex_type'] == v.PLEX_TYPE_SONG:
LOG.debug('Song has been played, not cleaning up playstate')
continue
try:
percentComplete = float(currentPosition) / float(runtime)
except ZeroDivisionError:
# Runtime is 0.
percentComplete = 0
LOG.info("Percent complete: %s Mark played at: %s"
% (percentComplete, v.MARK_PLAYED_AT))
if percentComplete >= v.MARK_PLAYED_AT:
resume = kodi_time_to_millis(status['time'])
runtime = kodi_time_to_millis(status['totaltime'])
LOG.info('Item playback progress %s out of %s', resume, runtime)
if not resume or not runtime:
continue
complete = float(resume) / float(runtime)
LOG.info("Percent complete: %s. Mark played at: %s",
complete, v.MARK_PLAYED_AT)
if complete >= v.MARK_PLAYED_AT:
# Tell Kodi that we've finished watching (Plex knows)
if (data['fileid'] is not None and
data['itemType'] in (v.KODI_TYPE_MOVIE,
v.KODI_TYPE_EPISODE)):
with kodidb.GetKodiDB('video') as kodi_db:
kodi_db.addPlaystate(
data['fileid'],
None,
None,
data['playcount'] + 1,
DateToKodi(getUnixTimestamp()))
# Clean the WINDOW properties
for filename in self.played_info:
plex_item = 'plex_%s' % tryEncode(filename)
cleanup = (
'%s.itemid' % plex_item,
'%s.runtime' % plex_item,
'%s.refreshid' % plex_item,
'%s.playmethod' % plex_item,
'%s.type' % plex_item,
'%s.runtime' % plex_item,
'%s.playcount' % plex_item,
'%s.playlistPosition' % plex_item,
'%s.subtitle' % plex_item,
)
for item in cleanup:
window(item, clear=True)
# Stop transcoding
if playMethod == "Transcode":
LOG.info("Transcoding for %s terminating" % itemid)
self.doUtils().downloadUrl(
"{server}/video/:/transcode/universal/stop",
parameters={'session': v.PKC_MACHINE_IDENTIFIER})
self.played_info.clear()
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(status['plex_id'])
file_id = plex_dbitem[1] if plex_dbitem else None
if file_id is None:
LOG.error('No file_id found for %s', status)
continue
with kodidb.GetKodiDB('video') as kodi_db:
kodi_db.addPlaystate(
file_id,
None,
None,
status['playcount'] + 1,
DateToKodi(getUnixTimestamp()))
LOG.info('Marked plex element %s as completely watched',
status['plex_id'])
# As all playback has halted, reset the players that have been active
state.ACTIVE_PLAYERS = []
for playerid in state.PLAYER_STATES:
state.PLAYER_STATES[playerid] = dict(state.PLAYSTATE)
LOG.info('Finished PKC playback cleanup')

View file

@ -190,6 +190,8 @@ class Playlist_Item(object):
guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
playcount = None [int] how many times the item has already been played
offset = None [int] the item's view offset UPON START in Plex time
part = 0 [int] part number if Plex video consists of mult. parts
init_done = False Set to True only if run through playback init
"""
@ -205,6 +207,8 @@ class Playlist_Item(object):
self.guid = None
self.xml = None
self.playmethod = None
self.playcount = None
self.offset = None
# If Plex video consists of several parts; part number
self.part = 0
self.init_done = False

View file

@ -82,44 +82,48 @@ COMMAND_PIPELINE_QUEUE = None
# Websocket_client queue to communicate with librarysync
WEBSOCKET_QUEUE = None
# Which Kodi player is/has been active? (either int 1, 2 or 3)
ACTIVE_PLAYERS = []
# Kodi player states - here, initial values are set
PLAYER_STATES = {
1: {
'type': 'movie',
'time': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0
},
'totaltime': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0
},
'speed': 0,
'shuffled': False,
'repeat': 'off',
'position': -1,
'playlistid': -1,
'currentvideostream': -1,
'currentaudiostream': -1,
'subtitleenabled': False,
'currentsubtitle': -1,
######
'file': '',
'kodi_id': None,
'kodi_type': None,
'plex_id': None,
'plex_type': None,
'container_key': None,
'volume': 100,
'muted': False
},
1: {},
2: {},
3: {}
}
# "empty" dict for the PLAYER_STATES above
PLAYSTATE = {
'type': None,
'time': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'totaltime': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'speed': 0,
'shuffled': False,
'repeat': 'off',
'position': None,
'playlistid': None,
'currentvideostream': -1,
'currentaudiostream': -1,
'subtitleenabled': False,
'currentsubtitle': -1,
'file': None,
'kodi_id': None,
'kodi_type': None,
'plex_id': None,
'plex_type': None,
'container_key': None,
'volume': 100,
'muted': False,
'playmethod': None,
'playcount': None
}
# Dict containing all filenames as keys with plex id as values - used for addon
# paths for playback (since we're not receiving a Kodi id)
PLEX_IDS = {}