mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-26 02:36:10 +00:00
c76f19e42d
With .55 the resume setting is set per library. Instead query server to see if the item is played to offer delete
392 lines
12 KiB
Python
392 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
#################################################################################################
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
|
|
import xbmc
|
|
import xbmcvfs
|
|
|
|
from helper import _, window, settings, dialog, JSONRPC
|
|
from emby import Emby
|
|
|
|
#################################################################################################
|
|
|
|
LOG = logging.getLogger("EMBY."+__name__)
|
|
|
|
#################################################################################################
|
|
|
|
|
|
class Player(xbmc.Player):
|
|
|
|
# Borg - multiple instances, shared state
|
|
_shared_state = {}
|
|
played = {}
|
|
|
|
def __init__(self):
|
|
|
|
self.__dict__ = self._shared_state
|
|
xbmc.Player.__init__(self)
|
|
|
|
def onPlayBackStarted(self):
|
|
|
|
''' We may need to wait for info to be set in kodi monitor.
|
|
Accounts for scenario where Kodi starts playback and exits immediately.
|
|
'''
|
|
count = 0
|
|
monitor = xbmc.Monitor()
|
|
|
|
try:
|
|
current_file = self.getPlayingFile()
|
|
except Exception:
|
|
|
|
while count < 5:
|
|
try:
|
|
current_file = self.getPlayingFile()
|
|
count = 0
|
|
break
|
|
except Exception:
|
|
count += 1
|
|
|
|
if monitor.waitForAbort(1):
|
|
return
|
|
else:
|
|
LOG.info('Cancel playback report')
|
|
|
|
return
|
|
|
|
items = window('emby_play.json')
|
|
item = None
|
|
|
|
while not items:
|
|
|
|
if monitor.waitForAbort(2):
|
|
return
|
|
|
|
items = window('emby_play.json')
|
|
count += 1
|
|
|
|
if count == 20:
|
|
LOG.info("Could not find emby prop...")
|
|
|
|
return
|
|
|
|
for item in items:
|
|
if item['Path'] == current_file.decode('utf-8'):
|
|
items.pop(items.index(item))
|
|
|
|
break
|
|
else:
|
|
item = items.pop(0)
|
|
|
|
window('emby_play.json', items)
|
|
|
|
self.set_item(current_file, item)
|
|
data = {
|
|
'QueueableMediaTypes': "Video,Audio",
|
|
'CanSeek': True,
|
|
'ItemId': item['Id'],
|
|
'MediaSourceId': item['MediaSourceId'],
|
|
'PlayMethod': item['PlayMethod'],
|
|
'VolumeLevel': item['Volume'],
|
|
'PositionTicks': int(item['CurrentPosition'] * 10000000),
|
|
'IsPaused': item['Paused'],
|
|
'IsMuted': item['Muted'],
|
|
'PlaySessionId': item['PlaySessionId'],
|
|
'AudioStreamIndex': item['AudioStreamIndex'],
|
|
'SubtitleStreamIndex': item['SubtitleStreamIndex']
|
|
}
|
|
item['Server']['api'].session_playing(data)
|
|
window('emby.skip.%s.bool' % item['Id'], True)
|
|
|
|
if monitor.waitForAbort(2):
|
|
return
|
|
|
|
self.set_audio_subs(item['AudioStreamIndex'], item['SubtitleStreamIndex'])
|
|
|
|
def set_item(self, file, item):
|
|
|
|
''' Set playback information.
|
|
'''
|
|
try:
|
|
item['Runtime'] = int(item['Runtime'])
|
|
except (TypeError, ValueError):
|
|
try:
|
|
item['Runtime'] = int(self.getTotalTime())
|
|
LOG.info("Runtime is missing, Kodi runtime: %s" % item['Runtime'])
|
|
except Exception:
|
|
item['Runtime'] = 0
|
|
LOG.info("Runtime is missing, Using Zero")
|
|
|
|
try:
|
|
seektime = self.getTime()
|
|
except Exception: # at this point we should be playing and if not then bail out
|
|
return
|
|
|
|
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
|
result = result.get('result', {})
|
|
volume = result.get('volume')
|
|
muted = result.get('muted')
|
|
|
|
item.update({
|
|
'File': file,
|
|
'CurrentPosition': item.get('CurrentPosition') or int(seektime),
|
|
'Muted': muted,
|
|
'Volume': volume,
|
|
'Server': Emby(item['ServerId']),
|
|
'Paused': False
|
|
})
|
|
|
|
self.played[file] = item
|
|
LOG.info("-->[ play/%s ] %s", item['Id'], item)
|
|
|
|
def set_audio_subs(self, audio=None, subtitle=None):
|
|
|
|
''' Only for after playback started
|
|
'''
|
|
LOG.info("Setting audio: %s subs: %s", audio, subtitle)
|
|
current_file = self.getPlayingFile()
|
|
|
|
if current_file in self.played:
|
|
|
|
item = self.played[current_file]
|
|
mapping = item['SubsMapping']
|
|
|
|
if audio and len(self.getAvailableAudioStreams()) > 1:
|
|
self.setAudioStream(audio - 1)
|
|
|
|
if subtitle is None:
|
|
return
|
|
|
|
tracks = len(self.getAvailableAudioStreams())
|
|
|
|
if subtitle == -1:
|
|
self.showSubtitles(False)
|
|
|
|
elif mapping:
|
|
for index in mapping:
|
|
|
|
if mapping[index] == subtitle:
|
|
self.setSubtitleStream(int(index))
|
|
|
|
break
|
|
else:
|
|
self.setSubtitleStream(len(mapping) + subtitle - tracks - 1)
|
|
else:
|
|
self.setSubtitleStream(subtitle - tracks - 1)
|
|
|
|
def detect_audio_subs(self, item):
|
|
|
|
params = {
|
|
'playerid': 1,
|
|
'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"]
|
|
}
|
|
result = JSONRPC('Player.GetProperties').execute(params)
|
|
result = result.get('result')
|
|
|
|
try: # Audio tracks
|
|
audio = result['currentaudiostream']['index']
|
|
except (KeyError, TypeError):
|
|
audio = 0
|
|
|
|
try: # Subtitles tracks
|
|
subs = result['currentsubtitle']['index']
|
|
except (KeyError, TypeError):
|
|
subs = 0
|
|
|
|
try: # If subtitles are enabled
|
|
subs_enabled = result['subtitleenabled']
|
|
except (KeyError, TypeError):
|
|
subs_enabled = False
|
|
|
|
item['AudioStreamIndex'] = audio + 1
|
|
|
|
if not subs_enabled or not len(self.getAvailableSubtitleStreams()):
|
|
item['SubtitleStreamIndex'] = None
|
|
|
|
return
|
|
|
|
mapping = item['SubsMapping']
|
|
tracks = len(self.getAvailableAudioStreams())
|
|
|
|
if mapping:
|
|
if str(subs) in mapping:
|
|
item['SubtitleStreamIndex'] = mapping[str(subs)]
|
|
else:
|
|
item['SubtitleStreamIndex'] = subs - len(mapping) + tracks + 1
|
|
else:
|
|
item['SubtitleStreamIndex'] = subs + tracks + 1
|
|
|
|
def onPlayBackPaused(self):
|
|
current_file = self.getPlayingFile()
|
|
|
|
if current_file in self.played:
|
|
|
|
self.played[current_file]['Paused'] = True
|
|
self.report_playback()
|
|
LOG.debug("-->[ paused ]")
|
|
|
|
def onPlayBackResumed(self):
|
|
current_file = self.getPlayingFile()
|
|
|
|
if current_file in self.played:
|
|
|
|
self.played[current_file]['Paused'] = False
|
|
self.report_playback()
|
|
LOG.debug("--<[ paused ]")
|
|
|
|
def onPlayBackSeek(self, time, seekOffset):
|
|
|
|
''' Does not seem to work in Leia??
|
|
'''
|
|
try:
|
|
current_file = self.getPlayingFile()
|
|
except Exception:
|
|
return
|
|
|
|
if current_file in self.played:
|
|
|
|
self.report_playback()
|
|
LOG.info("--[ seek ]")
|
|
|
|
def report_playback(self, report=True):
|
|
|
|
''' Report playback progress to emby server.
|
|
Check if the user seek.
|
|
'''
|
|
try:
|
|
current_file = self.getPlayingFile()
|
|
|
|
if current_file not in self.played:
|
|
return
|
|
except Exception as error:
|
|
LOG.error(error)
|
|
|
|
return
|
|
|
|
item = self.played[current_file]
|
|
|
|
if not report:
|
|
|
|
previous = item['CurrentPosition']
|
|
item['CurrentPosition'] = int(self.getTime())
|
|
|
|
if (item['CurrentPosition'] - previous) < 30:
|
|
|
|
return
|
|
|
|
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
|
result = result.get('result', {})
|
|
item['Volume'] = result.get('volume')
|
|
item['Muted'] = result.get('muted')
|
|
item['CurrentPosition'] = int(self.getTime())
|
|
self.detect_audio_subs(item)
|
|
|
|
data = {
|
|
'QueueableMediaTypes': "Video,Audio",
|
|
'CanSeek': True,
|
|
'ItemId': item['Id'],
|
|
'MediaSourceId': item['MediaSourceId'],
|
|
'PlayMethod': item['PlayMethod'],
|
|
'VolumeLevel': item['Volume'],
|
|
'PositionTicks': int(item['CurrentPosition'] * 10000000),
|
|
'IsPaused': item['Paused'],
|
|
'IsMuted': item['Muted'],
|
|
'PlaySessionId': item['PlaySessionId'],
|
|
'AudioStreamIndex': item['AudioStreamIndex'],
|
|
'SubtitleStreamIndex': item['SubtitleStreamIndex']
|
|
}
|
|
item['Server']['api'].session_progress(data)
|
|
|
|
def onPlayBackStopped(self):
|
|
|
|
''' Will be called when user stops playing a file.
|
|
'''
|
|
window('emby_play', clear=True)
|
|
self.stop_playback()
|
|
LOG.debug("--<[ playback ]")
|
|
|
|
def onPlayBackEnded(self):
|
|
|
|
''' Will be called when kodi stops playing a file.
|
|
'''
|
|
self.stop_playback()
|
|
LOG.debug("--<<[ playback ]")
|
|
|
|
def stop_playback(self):
|
|
|
|
''' Stop all playback. Check for external player for positionticks.
|
|
'''
|
|
if not self.played:
|
|
return
|
|
|
|
LOG.info("Played info: %s", self.played)
|
|
|
|
for file in self.played:
|
|
item = self.played[file]
|
|
|
|
if item:
|
|
window('emby.skip.%s.bool' % item['Id'], True)
|
|
|
|
if item['CurrentPosition'] and item['Runtime']:
|
|
|
|
try:
|
|
if window('emby.external.bool'):
|
|
window('emby.external', clear=True)
|
|
raise ValueError
|
|
|
|
played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime'])
|
|
except ZeroDivisionError: # Runtime is 0.
|
|
played = 0
|
|
except ValueError:
|
|
played = 100
|
|
item['CurrentPosition'] = int(item['Runtime'])
|
|
|
|
data = {
|
|
'ItemId': item['Id'],
|
|
'MediaSourceId': item['MediaSourceId'],
|
|
'PositionTicks': int(item['CurrentPosition'] * 10000000),
|
|
'PlaySessionId': item['PlaySessionId']
|
|
}
|
|
item['Server']['api'].session_stop(data)
|
|
|
|
if item.get('LiveStreamId'):
|
|
item['Server']['api'].close_live_stream(item['LiveStreamId'])
|
|
|
|
elif item['PlayMethod'] == 'Transcode':
|
|
|
|
LOG.info("<[ transcode/%s ]", item['Id'])
|
|
item['Server']['api'].close_transcode(item['DeviceId'])
|
|
|
|
|
|
path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8')
|
|
|
|
if xbmcvfs.exists(path):
|
|
dirs, files = xbmcvfs.listdir(path)
|
|
|
|
for file in files:
|
|
xbmcvfs.delete(os.path.join(path, file.decode('utf-8')))
|
|
|
|
result = item['Server']['api'].get_item(item['Id']) or {}
|
|
|
|
if 'UserData' in result and result['UserData']['Played']:
|
|
delete = False
|
|
|
|
if result['Type'] == 'Episode' and settings('deleteTV.bool'):
|
|
delete = True
|
|
elif result['Type'] == 'Movie' and settings('deleteMovies.bool'):
|
|
delete = True
|
|
|
|
if not settings('offerDelete.bool'):
|
|
delete = False
|
|
|
|
if delete:
|
|
|
|
if dialog("yesno", heading=_(30091), line1=_(33015), autoclose=120000):
|
|
item['Server']['api'].delete_item(item['Id'])
|
|
|
|
window('emby.external_check', clear=True)
|
|
|
|
self.played.clear()
|