mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-27 03:06:10 +00:00
1691fd5e8b
Add external subtitles as tracks for direct play and direct stream. Important to note, when direct streaming, the tracks are defined as Unknown(external). There's no way to correct this.
305 lines
No EOL
13 KiB
Python
305 lines
No EOL
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import json
|
|
import threading
|
|
import websocket
|
|
import logging
|
|
|
|
#################################################################################################
|
|
# WebSocket Client thread
|
|
#################################################################################################
|
|
|
|
import xbmc
|
|
import xbmcgui
|
|
import xbmcaddon
|
|
|
|
import KodiMonitor
|
|
import Utils as utils
|
|
from ClientInformation import ClientInformation
|
|
from DownloadUtils import DownloadUtils
|
|
from PlaybackUtils import PlaybackUtils
|
|
from LibrarySync import LibrarySync
|
|
from WriteKodiVideoDB import WriteKodiVideoDB
|
|
from WriteKodiMusicDB import WriteKodiMusicDB
|
|
|
|
logging.basicConfig()
|
|
|
|
|
|
class WebSocketThread(threading.Thread):
|
|
|
|
_shared_state = {}
|
|
|
|
doUtils = DownloadUtils()
|
|
clientInfo = ClientInformation()
|
|
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
|
|
|
addonName = clientInfo.getAddonName()
|
|
|
|
client = None
|
|
keepRunning = True
|
|
|
|
def __init__(self, *args):
|
|
|
|
self.__dict__ = self._shared_state
|
|
threading.Thread.__init__(self, *args)
|
|
|
|
def logMsg(self, msg, lvl=1):
|
|
|
|
self.className = self.__class__.__name__
|
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
|
|
|
def sendProgressUpdate(self, data):
|
|
self.logMsg("sendProgressUpdate", 1)
|
|
if self.client:
|
|
try:
|
|
# Send progress update
|
|
messageData = {
|
|
'MessageType': "ReportPlaybackProgress",
|
|
'Data': data
|
|
}
|
|
messageString = json.dumps(messageData)
|
|
self.client.send(messageString)
|
|
self.logMsg("Message data: %s" % messageString, 2)
|
|
except Exception as e:
|
|
self.logMsg("Exception: %s" % e, 1)
|
|
|
|
def stopClient(self):
|
|
# stopping the client is tricky, first set keep_running to false and then trigger one
|
|
# more message by requesting one SessionsStart message, this causes the
|
|
# client to receive the message and then exit
|
|
if self.client:
|
|
self.logMsg("Stopping Client")
|
|
self.keepRunning = False
|
|
self.client.keep_running = False
|
|
self.client.close()
|
|
self.logMsg("Stopping Client : KeepRunning set to False")
|
|
else:
|
|
self.logMsg("Stopping Client NO Object ERROR")
|
|
|
|
def on_message(self, ws, message):
|
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
self.logMsg("Message: %s" % message, 1)
|
|
|
|
result = json.loads(message)
|
|
messageType = result['MessageType']
|
|
data = result.get("Data")
|
|
|
|
if messageType == "Play":
|
|
# A remote control play command has been sent from the server.
|
|
itemIds = data['ItemIds']
|
|
playCommand = data['PlayCommand']
|
|
|
|
if "PlayNow" in playCommand:
|
|
startPositionTicks = data.get('StartPositionTicks', 0)
|
|
xbmc.executebuiltin("Dialog.Close(all,true)")
|
|
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
|
|
PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks)
|
|
|
|
elif "PlayNext" in playCommand:
|
|
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
|
|
playlist = PlaybackUtils().AddToPlaylist(itemIds)
|
|
if not xbmc.Player().isPlaying():
|
|
xbmc.Player().play(playlist)
|
|
|
|
elif messageType == "Playstate":
|
|
# A remote control update playstate command has been sent from the server.
|
|
command = data['Command']
|
|
|
|
if "Stop" in command:
|
|
self.logMsg("Playback Stopped.", 1)
|
|
xbmc.Player().stop()
|
|
elif "Unpause" in command:
|
|
self.logMsg("Playback unpaused.", 1)
|
|
xbmc.Player().pause()
|
|
elif "Pause" in command:
|
|
self.logMsg("Playback paused.", 1)
|
|
xbmc.Player().pause()
|
|
elif "NextTrack" in command:
|
|
self.logMsg("Playback next track.", 1)
|
|
xbmc.Player().playnext()
|
|
elif "PreviousTrack" in command:
|
|
self.logMsg("Playback previous track.", 1)
|
|
xbmc.Player().playprevious()
|
|
elif "Seek" in command:
|
|
seekPositionTicks = data['SeekPositionTicks']
|
|
seekTime = seekPositionTicks / 10000000.0
|
|
self.logMsg("Seek to %s" % seekTime, 1)
|
|
xbmc.Player().seekTime(seekTime)
|
|
# Report playback
|
|
WINDOW.setProperty('commandUpdate', "true")
|
|
|
|
elif messageType == "UserDataChanged":
|
|
# A user changed their personal rating for an item, or their playstate was updated
|
|
userdataList = data['UserDataList']
|
|
self.logMsg("Message: Doing UserDataChanged: UserDataList: %s" % userdataList, 1)
|
|
LibrarySync().user_data_update(userdataList)
|
|
|
|
elif messageType == "LibraryChanged":
|
|
# Library items
|
|
itemsRemoved = data.get("ItemsRemoved")
|
|
itemsAdded = data.get("ItemsAdded")
|
|
itemsUpdated = data.get("ItemsUpdated")
|
|
itemsToUpdate = itemsAdded + itemsUpdated
|
|
|
|
self.logMsg("Message: WebSocket LibraryChanged: Items Added: %s" % itemsAdded, 1)
|
|
self.logMsg("Message: WebSocket LibraryChanged: Items Updated: %s" % itemsUpdated, 1)
|
|
self.logMsg("Message: WebSocket LibraryChanged: Items Removed: %s" % itemsRemoved, 1)
|
|
|
|
LibrarySync().remove_items(itemsRemoved)
|
|
LibrarySync().update_items(itemsToUpdate)
|
|
|
|
elif messageType == "GeneralCommand":
|
|
|
|
command = data['Name']
|
|
arguments = data.get("Arguments")
|
|
|
|
if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
|
|
# These commands need to be reported back
|
|
if command == "Mute":
|
|
xbmc.executebuiltin('Mute')
|
|
elif command == "Unmute":
|
|
xbmc.executebuiltin('Mute')
|
|
elif command == "SetVolume":
|
|
volume = arguments['Volume']
|
|
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
|
|
elif command == "SetSubtitleStreamIndex":
|
|
# Emby merges audio and subtitle index together
|
|
xbmcplayer = xbmc.Player()
|
|
currentFile = xbmcplayer.getPlayingFile()
|
|
embyIndex = int(arguments['Index'])
|
|
|
|
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
|
|
externalIndex = json.loads(mapping)
|
|
|
|
if externalIndex:
|
|
# If there's external subtitles added via PlaybackUtils
|
|
for index in externalIndex:
|
|
if externalIndex[index] == embyIndex:
|
|
xbmcplayer.setSubtitleStream(int(index))
|
|
else:
|
|
# User selected internal subtitles
|
|
external = len(externalIndex)
|
|
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
|
|
xbmcplayer.setSubtitleStream(external + embyIndex - audioTracks - 1)
|
|
else:
|
|
# Emby merges audio and subtitle index together
|
|
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
|
|
xbmcplayer.setSubtitleStream(index - audioTracks - 1)
|
|
|
|
elif command == "SetAudioStreamIndex":
|
|
index = int(arguments['Index'])
|
|
xbmc.Player().setAudioStream(index - 1)
|
|
# Report playback
|
|
WINDOW.setProperty('commandUpdate', "true")
|
|
|
|
else:
|
|
# GUI commands
|
|
if command == "ToggleFullscreen":
|
|
xbmc.executebuiltin('Action(FullScreen)')
|
|
elif command == "ToggleOsdMenu":
|
|
xbmc.executebuiltin('Action(OSD)')
|
|
elif command == "MoveUp":
|
|
xbmc.executebuiltin('Action(Up)')
|
|
elif command == "MoveDown":
|
|
xbmc.executebuiltin('Action(Down)')
|
|
elif command == "MoveLeft":
|
|
xbmc.executebuiltin('Action(Left)')
|
|
elif command == "MoveRight":
|
|
xbmc.executebuiltin('Action(Right)')
|
|
elif command == "Select":
|
|
xbmc.executebuiltin('Action(Select)')
|
|
elif command == "Back":
|
|
xbmc.executebuiltin('Action(back)')
|
|
elif command == "ToggleContextMenu":
|
|
xbmc.executebuiltin('Action(ContextMenu)')
|
|
elif command == "GoHome":
|
|
xbmc.executebuiltin('ActivateWindow(Home)')
|
|
elif command == "PageUp":
|
|
xbmc.executebuiltin('Action(PageUp)')
|
|
elif command == "NextLetter":
|
|
xbmc.executebuiltin('Action(NextLetter)')
|
|
elif command == "GoToSearch":
|
|
xbmc.executebuiltin('VideoLibrary.Search')
|
|
elif command == "GoToSettings":
|
|
xbmc.executebuiltin('ActivateWindow(Settings)')
|
|
elif command == "PageDown":
|
|
xbmc.executebuiltin('Action(PageDown)')
|
|
elif command == "PreviousLetter":
|
|
xbmc.executebuiltin('Action(PrevLetter)')
|
|
elif command == "TakeScreenshot":
|
|
xbmc.executebuiltin('TakeScreenshot')
|
|
elif command == "ToggleMute":
|
|
xbmc.executebuiltin('Mute')
|
|
elif command == "VolumeUp":
|
|
xbmc.executebuiltin('Action(VolumeUp)')
|
|
elif command == "VolumeDown":
|
|
xbmc.executebuiltin('Action(VolumeDown)')
|
|
elif command == "DisplayMessage":
|
|
header = arguments['Header']
|
|
text = arguments['Text']
|
|
xbmcgui.Dialog().notification(header, text, icon="special://home/addons/plugin.video.emby/icon.png", time=4000)
|
|
elif command == "SendString":
|
|
string = arguments['String']
|
|
text = '{"jsonrpc": "2.0", "method": "Input.SendText", "params": { "text": "%s", "done": false }, "id": 0}' % string
|
|
result = xbmc.executeJSONRPC(text)
|
|
else:
|
|
self.logMsg("Unknown command.", 1)
|
|
|
|
elif messageType == "ServerRestarting":
|
|
if utils.settings('supressRestartMsg') == "true":
|
|
xbmcgui.Dialog().notification("Emby server", "Server is restarting.", icon="special://home/addons/plugin.video.emby/icon.png")
|
|
|
|
def on_error(self, ws, error):
|
|
if "10061" in str(error):
|
|
# Server is offline
|
|
pass
|
|
else:
|
|
self.logMsg("Error: %s" % error, 1)
|
|
#raise
|
|
|
|
def on_close(self, ws):
|
|
self.logMsg("Closed", 2)
|
|
|
|
def on_open(self, ws):
|
|
deviceId = ClientInformation().getMachineId()
|
|
self.doUtils.postCapabilities(deviceId)
|
|
|
|
def run(self):
|
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
logLevel = int(WINDOW.getProperty('getLogLevel'))
|
|
username = WINDOW.getProperty('currUser')
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
token = WINDOW.getProperty('accessToken%s' % username)
|
|
deviceId = ClientInformation().getMachineId()
|
|
|
|
'''if (logLevel == 2):
|
|
websocket.enableTrace(True)'''
|
|
|
|
# Get the appropriate prefix for websocket
|
|
if "https" in server:
|
|
server = server.replace('https', 'wss')
|
|
else:
|
|
server = server.replace('http', 'ws')
|
|
|
|
websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
|
|
self.logMsg("websocket URL: %s" % websocketUrl)
|
|
|
|
self.client = websocket.WebSocketApp(websocketUrl,
|
|
on_message = self.on_message,
|
|
on_error = self.on_error,
|
|
on_close = self.on_close)
|
|
|
|
self.client.on_open = self.on_open
|
|
|
|
while self.keepRunning:
|
|
|
|
self.client.run_forever()
|
|
|
|
if self.keepRunning:
|
|
self.logMsg("Client Needs To Restart", 2)
|
|
if self.KodiMonitor.waitForAbort(5):
|
|
break
|
|
|
|
self.logMsg("Thread Exited", 1) |