mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-11-10 12:16:12 +00:00
Support up next
This commit is contained in:
parent
5808c449c4
commit
c3fbdf082c
5 changed files with 110 additions and 18 deletions
|
@ -159,6 +159,13 @@ def get_next(index=None, limit=1):
|
||||||
'StartIndex': None if index is None else int(index)
|
'StartIndex': None if index is None else int(index)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_adjacent_episodes(show_id, item_id):
|
||||||
|
return shows("/%s/Episodes" % show_id, {
|
||||||
|
'UserId': "{UserId}",
|
||||||
|
'AdjacentTo': item_id,
|
||||||
|
'Fields': "Overview"
|
||||||
|
})
|
||||||
|
|
||||||
def get_genres(parent_id=None):
|
def get_genres(parent_id=None):
|
||||||
return _get("Genres", {
|
return _get("Genres", {
|
||||||
'ParentId': parent_id,
|
'ParentId': parent_id,
|
||||||
|
|
|
@ -46,7 +46,8 @@ def set_properties(item, method, server_id=None):
|
||||||
'SubsMapping': info.get('Subtitles'),
|
'SubsMapping': info.get('Subtitles'),
|
||||||
'AudioStreamIndex': info.get('AudioStreamIndex'),
|
'AudioStreamIndex': info.get('AudioStreamIndex'),
|
||||||
'SubtitleStreamIndex': info.get('SubtitleStreamIndex'),
|
'SubtitleStreamIndex': info.get('SubtitleStreamIndex'),
|
||||||
'CurrentPosition': info.get('CurrentPosition')
|
'CurrentPosition': info.get('CurrentPosition'),
|
||||||
|
'CurrentEpisode': info.get('CurrentEpisode')
|
||||||
})
|
})
|
||||||
|
|
||||||
window('emby_play.json', current)
|
window('emby_play.json', current)
|
||||||
|
@ -400,6 +401,14 @@ class PlayUtils(object):
|
||||||
}
|
}
|
||||||
if settings('transcode_h265.bool'):
|
if settings('transcode_h265.bool'):
|
||||||
profile['DirectPlayProfiles'][0]['VideoCodec'] = "h264,mpeg4,mpeg2video"
|
profile['DirectPlayProfiles'][0]['VideoCodec'] = "h264,mpeg4,mpeg2video"
|
||||||
|
else:
|
||||||
|
profile['TranscodingProfiles'].insert(0, {
|
||||||
|
"Container": "m3u8",
|
||||||
|
"Type": "Video",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video",
|
||||||
|
"MaxAudioChannels": "6"
|
||||||
|
})
|
||||||
|
|
||||||
if settings('transcodeHi10P.bool'):
|
if settings('transcodeHi10P.bool'):
|
||||||
profile['CodecProfiles'].append(
|
profile['CodecProfiles'].append(
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
import binascii
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -127,13 +128,20 @@ def find(dict, item):
|
||||||
if re.match(key, item, re.I):
|
if re.match(key, item, re.I):
|
||||||
return dict[key]
|
return dict[key]
|
||||||
|
|
||||||
def event(method, data=None):
|
def event(method, data=None, sender=None, hexlify=False):
|
||||||
|
|
||||||
''' Data is a dictionary.
|
''' Data is a dictionary.
|
||||||
'''
|
'''
|
||||||
data = data or {}
|
data = data or {}
|
||||||
xbmc.executebuiltin('NotifyAll(plugin.video.emby, %s, "[%s]")' % (method, json.dumps(data).replace('"', '\\"')))
|
sender = sender or "plugin.video.emby"
|
||||||
LOG.debug("---[ event: %s ] %s", method, data)
|
|
||||||
|
if hexlify:
|
||||||
|
data = '\\"[\\"{0}\\"]\\"'.format(binascii.hexlify(json.dumps(data)))
|
||||||
|
else:
|
||||||
|
data = '"[%s]"' % json.dumps(data).replace('"', '\\"')
|
||||||
|
|
||||||
|
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
|
||||||
|
LOG.debug("---[ event: %s/%s ] %s", sender, method, data)
|
||||||
|
|
||||||
def dialog(dialog_type, *args, **kwargs):
|
def dialog(dialog_type, *args, **kwargs):
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
import binascii
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
@ -46,7 +47,7 @@ class Monitor(xbmc.Monitor):
|
||||||
|
|
||||||
def onNotification(self, sender, method, data):
|
def onNotification(self, sender, method, data):
|
||||||
|
|
||||||
if sender.lower() not in ('plugin.video.emby', 'xbmc'):
|
if sender.lower() not in ('plugin.video.emby', 'xbmc', 'upnextprovider'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if sender == 'plugin.video.emby':
|
if sender == 'plugin.video.emby':
|
||||||
|
@ -61,6 +62,18 @@ class Monitor(xbmc.Monitor):
|
||||||
return
|
return
|
||||||
|
|
||||||
data = json.loads(data)[0]
|
data = json.loads(data)[0]
|
||||||
|
|
||||||
|
elif sender == 'upnextprovider':
|
||||||
|
method = method.split('.')[1]
|
||||||
|
|
||||||
|
if method not in ('plugin.video.emby_play_action'):
|
||||||
|
return
|
||||||
|
|
||||||
|
data = json.loads(data)
|
||||||
|
method = "Play"
|
||||||
|
|
||||||
|
if data:
|
||||||
|
data = json.loads(binascii.unhexlify(data[0]))
|
||||||
else:
|
else:
|
||||||
if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'Player.OnAVChange'):
|
if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'Player.OnAVChange'):
|
||||||
return
|
return
|
||||||
|
|
|
@ -9,7 +9,8 @@ import os
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
from helper import _, window, settings, dialog, JSONRPC
|
from objects.obj import Objects
|
||||||
|
from helper import _, api, window, settings, dialog, event, JSONRPC
|
||||||
from emby import Emby
|
from emby import Emby
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
@ -24,6 +25,7 @@ class Player(xbmc.Player):
|
||||||
# Borg - multiple instances, shared state
|
# Borg - multiple instances, shared state
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
played = {}
|
played = {}
|
||||||
|
up_next = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ class Player(xbmc.Player):
|
||||||
''' We may need to wait for info to be set in kodi monitor.
|
''' We may need to wait for info to be set in kodi monitor.
|
||||||
Accounts for scenario where Kodi starts playback and exits immediately.
|
Accounts for scenario where Kodi starts playback and exits immediately.
|
||||||
'''
|
'''
|
||||||
|
self.up_next = False
|
||||||
count = 0
|
count = 0
|
||||||
monitor = xbmc.Monitor()
|
monitor = xbmc.Monitor()
|
||||||
|
|
||||||
|
@ -219,6 +222,49 @@ class Player(xbmc.Player):
|
||||||
else:
|
else:
|
||||||
item['SubtitleStreamIndex'] = subs + tracks + 1
|
item['SubtitleStreamIndex'] = subs + tracks + 1
|
||||||
|
|
||||||
|
def next_up(self):
|
||||||
|
|
||||||
|
current_file = self.getPlayingFile()
|
||||||
|
item = self.played[current_file]
|
||||||
|
objects = Objects()
|
||||||
|
|
||||||
|
if item['Type'] != 'Episode':
|
||||||
|
return
|
||||||
|
|
||||||
|
next_items = item['Server']['api'].get_adjacent_episodes(item['CurrentEpisode']['tvshowid'], item['Id'])
|
||||||
|
|
||||||
|
for index, next_item in enumerate(next_items['Items']):
|
||||||
|
if next_item['Id'] == item['Id']:
|
||||||
|
|
||||||
|
try:
|
||||||
|
next_item = next_items['Items'][index + 1]
|
||||||
|
except IndexError:
|
||||||
|
LOG.warn("No next up episode.")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
API = api.API(next_item, item['Server']['auth/server-address'])
|
||||||
|
data = objects.map(next_item, "UpNext")
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
if artwork['Backdrop']:
|
||||||
|
data['art']['tvshow.fanart'] = artwork['Backdrop'][0]
|
||||||
|
|
||||||
|
next_info = {
|
||||||
|
'play_info': {'ItemIds': [data['episodeid']], 'ServerId': item['ServerId'], 'PlayCommand': 'PlayNow'},
|
||||||
|
'current_episode': item['CurrentEpisode'],
|
||||||
|
'next_episode': data
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("--[ next up ] %s", json.dumps(next_info, indent=4))
|
||||||
|
event("upnext_data", next_info, hexlify=True)
|
||||||
|
|
||||||
def onPlayBackPaused(self):
|
def onPlayBackPaused(self):
|
||||||
current_file = self.getPlayingFile()
|
current_file = self.getPlayingFile()
|
||||||
|
|
||||||
|
@ -268,15 +314,32 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
item = self.played[current_file]
|
item = self.played[current_file]
|
||||||
|
|
||||||
|
if window('emby.external.bool'):
|
||||||
|
return
|
||||||
|
|
||||||
if not report:
|
if not report:
|
||||||
|
|
||||||
previous = item['CurrentPosition']
|
previous = item['CurrentPosition']
|
||||||
item['CurrentPosition'] = int(self.getTime())
|
item['CurrentPosition'] = int(self.getTime())
|
||||||
|
|
||||||
|
if int(item['CurrentPosition']) == 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) * 100
|
||||||
|
except ZeroDivisionError: # Runtime is 0.
|
||||||
|
played = 0
|
||||||
|
|
||||||
|
if played > 2.0 and not self.up_next:
|
||||||
|
|
||||||
|
self.up_next = True
|
||||||
|
self.next_up()
|
||||||
|
|
||||||
if (item['CurrentPosition'] - previous) < 30:
|
if (item['CurrentPosition'] - previous) < 30:
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
||||||
result = result.get('result', {})
|
result = result.get('result', {})
|
||||||
item['Volume'] = result.get('volume')
|
item['Volume'] = result.get('volume')
|
||||||
|
@ -330,18 +393,10 @@ class Player(xbmc.Player):
|
||||||
if item:
|
if item:
|
||||||
window('emby.skip.%s.bool' % item['Id'], True)
|
window('emby.skip.%s.bool' % item['Id'], True)
|
||||||
|
|
||||||
if item['CurrentPosition'] and item['Runtime']:
|
|
||||||
|
|
||||||
try:
|
|
||||||
if window('emby.external.bool'):
|
if window('emby.external.bool'):
|
||||||
window('emby.external', clear=True)
|
window('emby.external', clear=True)
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime'])
|
if int(item['CurrentPosition']) == 1:
|
||||||
except ZeroDivisionError: # Runtime is 0.
|
|
||||||
played = 0
|
|
||||||
except ValueError:
|
|
||||||
played = 100
|
|
||||||
item['CurrentPosition'] = int(item['Runtime'])
|
item['CurrentPosition'] = int(item['Runtime'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
|
Loading…
Reference in a new issue