jellyfin-kodi/jellyfin_kodi/player.py

475 lines
15 KiB
Python
Raw Normal View History

2019-07-09 20:05:28 +00:00
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
2019-07-09 20:05:28 +00:00
#################################################################################################
import os
2024-06-11 05:10:57 +00:00
import xbmc
import xbmcvfs
2019-07-09 20:05:28 +00:00
2021-10-10 18:38:25 +00:00
from .objects.obj import Objects
from .helper import translate, api, window, settings, dialog, event, JSONRPC
from .jellyfin import Jellyfin
from .helper import LazyLogger
from .helper.utils import translate_path
2019-07-09 20:05:28 +00:00
#################################################################################################
2020-04-19 01:05:59 +00:00
LOG = LazyLogger(__name__)
2019-07-09 20:05:28 +00:00
#################################################################################################
class Player(xbmc.Player):
played = {}
up_next = False
def __init__(self):
xbmc.Player.__init__(self)
def get_playing_file(self):
try:
return self.getPlayingFile()
except Exception as error:
LOG.exception(error)
2019-07-09 20:05:28 +00:00
def get_file_info(self, file):
try:
return self.played[file]
except Exception as error:
LOG.exception(error)
2019-07-09 20:05:28 +00:00
def is_playing_file(self, file):
return file in self.played
def onPlayBackStarted(self):
2024-06-10 09:19:47 +00:00
"""We may need to wait for info to be set in kodi monitor.
Accounts for scenario where Kodi starts playback and exits immediately.
First, ensure previous playback terminated correctly in Jellyfin.
"""
2019-07-09 20:05:28 +00:00
self.stop_playback()
self.up_next = False
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:
2024-06-10 09:19:47 +00:00
LOG.info("Cancel playback report")
2019-07-09 20:05:28 +00:00
return
2024-06-10 09:19:47 +00:00
items = window("jellyfin_play.json")
2019-07-09 20:05:28 +00:00
item = None
while not items:
if monitor.waitForAbort(2):
return
2024-06-10 09:19:47 +00:00
items = window("jellyfin_play.json")
2019-07-09 20:05:28 +00:00
count += 1
if count == 20:
LOG.info("Could not find jellyfin prop...")
return
for item in items:
2024-06-10 09:19:47 +00:00
if item["Path"] == current_file:
2019-07-09 20:05:28 +00:00
items.pop(items.index(item))
break
else:
item = items.pop(0)
2024-06-10 09:19:47 +00:00
window("jellyfin_play.json", items)
2019-07-09 20:05:28 +00:00
self.set_item(current_file, item)
data = {
2024-06-10 09:19:47 +00:00
"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"],
2019-07-09 20:05:28 +00:00
}
2024-06-10 09:19:47 +00:00
item["Server"].jellyfin.session_playing(data)
window("jellyfin.skip.%s.bool" % item["Id"], True)
2019-07-09 20:05:28 +00:00
if monitor.waitForAbort(2):
return
2024-06-10 09:19:47 +00:00
if item["PlayOption"] == "Addon":
self.set_audio_subs(item["AudioStreamIndex"], item["SubtitleStreamIndex"])
2019-07-09 20:05:28 +00:00
def set_item(self, file, item):
2024-06-10 09:19:47 +00:00
"""Set playback information."""
2019-07-09 20:05:28 +00:00
try:
2024-06-10 09:19:47 +00:00
item["Runtime"] = int(item["Runtime"])
2019-07-09 20:05:28 +00:00
except (TypeError, ValueError):
try:
2024-06-10 09:19:47 +00:00
item["Runtime"] = int(self.getTotalTime())
LOG.info("Runtime is missing, Kodi runtime: %s" % item["Runtime"])
2019-07-09 20:05:28 +00:00
except Exception:
2024-06-10 09:19:47 +00:00
item["Runtime"] = 0
2019-07-09 20:05:28 +00:00
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
2024-06-10 09:19:47 +00:00
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": Jellyfin(item["ServerId"]).get_client(),
"Paused": False,
}
)
2019-07-09 20:05:28 +00:00
self.played[file] = item
2024-06-10 09:19:47 +00:00
LOG.info("-->[ play/%s ] %s", item["Id"], item)
2019-07-09 20:05:28 +00:00
def set_audio_subs(self, audio=None, subtitle=None):
if audio:
2020-08-21 12:56:15 +00:00
audio = int(audio)
if subtitle:
2020-08-21 12:56:15 +00:00
subtitle = int(subtitle)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
""" Only for after playback started
"""
2019-07-09 20:05:28 +00:00
LOG.info("Setting audio: %s subs: %s", audio, subtitle)
current_file = self.get_playing_file()
if self.is_playing_file(current_file):
item = self.get_file_info(current_file)
2024-06-10 09:19:47 +00:00
mapping = item["SubsMapping"]
2019-07-09 20:05:28 +00:00
if audio and len(self.getAvailableAudioStreams()) > 1:
self.setAudioStream(audio - 1)
if subtitle is None or subtitle == -1:
2019-07-09 20:05:28 +00:00
self.showSubtitles(False)
return
tracks = len(self.getAvailableAudioStreams())
if 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 = {
2024-06-10 09:19:47 +00:00
"playerid": 1,
"properties": ["currentsubtitle", "currentaudiostream", "subtitleenabled"],
2019-07-09 20:05:28 +00:00
}
2024-06-10 09:19:47 +00:00
result = JSONRPC("Player.GetProperties").execute(params)
result = result.get("result")
2019-07-09 20:05:28 +00:00
try: # Audio tracks
2024-06-10 09:19:47 +00:00
audio = result["currentaudiostream"]["index"]
2019-07-09 20:05:28 +00:00
except (KeyError, TypeError):
audio = 0
try: # Subtitles tracks
2024-06-10 09:19:47 +00:00
subs = result["currentsubtitle"]["index"]
2019-07-09 20:05:28 +00:00
except (KeyError, TypeError):
subs = 0
try: # If subtitles are enabled
2024-06-10 09:19:47 +00:00
subs_enabled = result["subtitleenabled"]
2019-07-09 20:05:28 +00:00
except (KeyError, TypeError):
subs_enabled = False
2024-06-10 09:19:47 +00:00
item["AudioStreamIndex"] = audio + 1
2019-07-09 20:05:28 +00:00
if not subs_enabled or not len(self.getAvailableSubtitleStreams()):
2024-06-10 09:19:47 +00:00
item["SubtitleStreamIndex"] = None
2019-07-09 20:05:28 +00:00
return
2024-06-10 09:19:47 +00:00
mapping = item["SubsMapping"]
2019-07-09 20:05:28 +00:00
tracks = len(self.getAvailableAudioStreams())
if mapping:
if str(subs) in mapping:
2024-06-10 09:19:47 +00:00
item["SubtitleStreamIndex"] = mapping[str(subs)]
2019-07-09 20:05:28 +00:00
else:
2024-06-10 09:19:47 +00:00
item["SubtitleStreamIndex"] = subs - len(mapping) + tracks + 1
2019-07-09 20:05:28 +00:00
else:
2024-06-10 09:19:47 +00:00
item["SubtitleStreamIndex"] = subs + tracks + 1
2019-07-09 20:05:28 +00:00
def next_up(self):
item = self.get_file_info(self.get_playing_file())
objects = Objects()
2024-06-10 09:19:47 +00:00
if item["Type"] != "Episode" or not item.get("CurrentEpisode"):
2019-07-09 20:05:28 +00:00
return
2024-06-10 09:19:47 +00:00
next_items = item["Server"].jellyfin.get_adjacent_episodes(
item["CurrentEpisode"]["tvshowid"], item["Id"]
)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
for index, next_item in enumerate(next_items["Items"]):
if next_item["Id"] == item["Id"]:
2019-07-09 20:05:28 +00:00
try:
2024-06-10 09:19:47 +00:00
next_item = next_items["Items"][index + 1]
2019-07-09 20:05:28 +00:00
except IndexError:
LOG.warning("No next up episode.")
2019-07-09 20:05:28 +00:00
return
break
2024-06-10 09:19:47 +00:00
server_address = item["Server"].auth.get_server_info(
item["Server"].auth.server_id
)["address"]
API = api.API(next_item, server_address)
2019-07-09 20:05:28 +00:00
data = objects.map(next_item, "UpNext")
2024-06-10 09:19:47 +00:00
artwork = API.get_all_artwork(objects.map(next_item, "ArtworkParent"), True)
data["art"] = {
"tvshow.poster": artwork.get("Series.Primary"),
"tvshow.fanart": None,
"thumb": artwork.get("Primary"),
2019-07-09 20:05:28 +00:00
}
2024-06-10 09:19:47 +00:00
if artwork["Backdrop"]:
data["art"]["tvshow.fanart"] = artwork["Backdrop"][0]
2019-07-09 20:05:28 +00:00
next_info = {
2024-06-10 09:19:47 +00:00
"play_info": {
"ItemIds": [data["episodeid"]],
"ServerId": item["ServerId"],
"PlayCommand": "PlayNow",
},
"current_episode": item["CurrentEpisode"],
"next_episode": data,
2019-07-09 20:05:28 +00:00
}
LOG.info("--[ next up ] %s", next_info)
event("upnext_data", next_info, hexlify=True)
def onPlayBackPaused(self):
current_file = self.get_playing_file()
if self.is_playing_file(current_file):
2024-06-10 09:19:47 +00:00
self.get_file_info(current_file)["Paused"] = True
2019-07-09 20:05:28 +00:00
self.report_playback()
LOG.debug("-->[ paused ]")
def onPlayBackResumed(self):
current_file = self.get_playing_file()
if self.is_playing_file(current_file):
2024-06-10 09:19:47 +00:00
self.get_file_info(current_file)["Paused"] = False
2019-07-09 20:05:28 +00:00
self.report_playback()
LOG.debug("--<[ paused ]")
def onPlayBackSeek(self, time, seek_offset):
2024-06-10 09:19:47 +00:00
"""Does not seem to work in Leia??"""
2019-07-09 20:05:28 +00:00
if self.is_playing_file(self.get_playing_file()):
self.report_playback()
LOG.info("--[ seek ]")
def report_playback(self, report=True):
2024-06-10 09:19:47 +00:00
"""Report playback progress to jellyfin server.
Check if the user seek.
"""
2019-07-09 20:05:28 +00:00
current_file = self.get_playing_file()
if not self.is_playing_file(current_file):
return
item = self.get_file_info(current_file)
2024-06-10 09:19:47 +00:00
if window("jellyfin.external.bool"):
2019-07-09 20:05:28 +00:00
return
if not report:
2024-06-10 09:19:47 +00:00
previous = item["CurrentPosition"]
try:
2024-06-10 09:19:47 +00:00
item["CurrentPosition"] = int(self.getTime())
2024-02-07 07:54:53 +00:00
except Exception as e:
# getTime() raises RuntimeError if nothing is playing
LOG.debug("Failed to get playback position: %s", e)
return
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
if int(item["CurrentPosition"]) == 1:
2019-07-09 20:05:28 +00:00
return
try:
2024-06-10 09:19:47 +00:00
played = (
float(item["CurrentPosition"] * 10000000)
/ int(item["Runtime"])
* 100
)
2019-10-03 02:14:54 +00:00
except ZeroDivisionError: # Runtime is 0.
2019-07-09 20:05:28 +00:00
played = 0
if played > 2.0 and not self.up_next:
self.up_next = True
self.next_up()
2024-06-10 09:19:47 +00:00
if (item["CurrentPosition"] - previous) < 30:
2019-07-09 20:05:28 +00:00
return
2024-06-10 09:19:47 +00:00
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())
2019-07-09 20:05:28 +00:00
self.detect_audio_subs(item)
data = {
2024-06-10 09:19:47 +00:00
"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"],
2019-07-09 20:05:28 +00:00
}
2024-06-10 09:19:47 +00:00
item["Server"].jellyfin.session_progress(data)
2019-07-09 20:05:28 +00:00
def onPlayBackStopped(self):
2024-06-10 09:19:47 +00:00
"""Will be called when user stops playing a file."""
window("jellyfin_play", clear=True)
2019-07-09 20:05:28 +00:00
self.stop_playback()
LOG.info("--<[ playback ]")
def onPlayBackEnded(self):
2024-06-10 09:19:47 +00:00
"""Will be called when kodi stops playing a file."""
2019-07-09 20:05:28 +00:00
self.stop_playback()
LOG.info("--<<[ playback ]")
def stop_playback(self):
2024-06-10 09:19:47 +00:00
"""Stop all playback. Check for external player for positionticks."""
2019-07-09 20:05:28 +00:00
if not self.played:
return
LOG.info("Played info: %s", self.played)
for file in self.played:
item = self.get_file_info(file)
2024-06-10 09:19:47 +00:00
window("jellyfin.skip.%s.bool" % item["Id"], True)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
if window("jellyfin.external.bool"):
window("jellyfin.external", clear=True)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
if int(item["CurrentPosition"]) == 1:
item["CurrentPosition"] = int(item["Runtime"])
2019-07-09 20:05:28 +00:00
data = {
2024-06-10 09:19:47 +00:00
"ItemId": item["Id"],
"MediaSourceId": item["MediaSourceId"],
"PositionTicks": int(item["CurrentPosition"] * 10000000),
"PlaySessionId": item["PlaySessionId"],
2019-07-09 20:05:28 +00:00
}
2024-06-10 09:19:47 +00:00
item["Server"].jellyfin.session_stop(data)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
if item.get("LiveStreamId"):
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
LOG.info("<[ livestream/%s ]", item["LiveStreamId"])
item["Server"].jellyfin.close_live_stream(item["LiveStreamId"])
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
elif item["PlayMethod"] == "Transcode":
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
LOG.info("<[ transcode/%s ]", item["Id"])
item["Server"].jellyfin.close_transcode(
item["DeviceId"], item["PlaySessionId"]
)
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
path = translate_path(
"special://profile/addon_data/plugin.video.jellyfin/temp/"
)
2019-07-09 20:05:28 +00:00
if xbmcvfs.exists(path):
dirs, files = xbmcvfs.listdir(path)
for file in files:
2021-07-08 23:19:18 +00:00
# Only delete the cached files for the previous play session
2024-06-10 09:19:47 +00:00
if item["Id"] in file:
2021-07-08 23:19:18 +00:00
xbmcvfs.delete(os.path.join(path, file))
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
result = item["Server"].jellyfin.get_item(item["Id"]) or {}
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
if "UserData" in result and result["UserData"]["Played"]:
2019-07-09 20:05:28 +00:00
delete = False
2024-06-10 09:19:47 +00:00
if result["Type"] == "Episode" and settings("deleteTV.bool"):
2019-07-09 20:05:28 +00:00
delete = True
2024-06-10 09:19:47 +00:00
elif result["Type"] == "Movie" and settings("deleteMovies.bool"):
2019-07-09 20:05:28 +00:00
delete = True
2024-06-10 09:19:47 +00:00
if not settings("offerDelete.bool"):
2019-07-09 20:05:28 +00:00
delete = False
if delete:
LOG.info("Offer delete option")
2024-06-10 09:19:47 +00:00
if dialog(
"yesno", translate(30091), translate(33015), autoclose=120000
):
item["Server"].jellyfin.delete_item(item["Id"])
2019-07-09 20:05:28 +00:00
2024-06-10 09:19:47 +00:00
window("jellyfin.external_check", clear=True)
2019-07-09 20:05:28 +00:00
self.played.clear()