mirror of
https://github.com/sakaljurgis/plugin.video.sklk.git
synced 2026-11-06 14:38:03 +00:00
Compare commits
2
Commits
99b8f071fe
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e20067d6d | ||
|
|
227a95abcd |
No files matched your search
@@ -8,4 +8,14 @@ Do **not** try to install a .zip generated by GitHub.
|
|||||||
|
|
||||||
Compatible with Kodi 19.0 ("Matrix") and above that uses Python 3.
|
Compatible with Kodi 19.0 ("Matrix") and above that uses Python 3.
|
||||||
|
|
||||||
|
Settings: the server URL (e.g. `https://media.example.com`; a value ending with `/api`, as older
|
||||||
|
versions had it, works too) and an optional auth key, sent as the `Authorization` header.
|
||||||
|
|
||||||
|
Watched state and resume points live on the server, shared with other apps (e.g. SKLK TV): listings
|
||||||
|
come with them, and a background service (`service.py`) reports playback (every 30 s, on pause, stop
|
||||||
|
and end). Kodi's own earlier watched history of this add-on can be sent to the server with the button
|
||||||
|
in the add-on settings ("Send this Kodi's watched history to the server"); do it on the Kodi whose
|
||||||
|
history counts, the server keeps whatever it has that's newer. Mark watched/unwatched with the add-on's own context menu entries ("Mark watched",
|
||||||
|
"Mark all watched", ...); Kodi's built-in "Mark as watched" only changes Kodi's local database.
|
||||||
|
|
||||||
License: no licence
|
License: no licence
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<addon id="plugin.video.sklk"
|
<addon id="plugin.video.sklk"
|
||||||
version="0.0.23"
|
version="0.0.25"
|
||||||
name="sklk"
|
name="sklk"
|
||||||
provider-name="krk">
|
provider-name="krk">
|
||||||
<requires>
|
<requires>
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
<extension point="xbmc.python.pluginsource" library="main.py">
|
<extension point="xbmc.python.pluginsource" library="main.py">
|
||||||
<provides>video</provides>
|
<provides>video</provides>
|
||||||
</extension>
|
</extension>
|
||||||
|
<!-- reports playback to the server, which keeps watched state for all apps -->
|
||||||
|
<extension point="xbmc.service" library="service.py" start="login"/>
|
||||||
<extension point="xbmc.addon.metadata">
|
<extension point="xbmc.addon.metadata">
|
||||||
<summary lang="en">sklk pluginas</summary>
|
<summary lang="en">sklk pluginas</summary>
|
||||||
<description lang="en_GB">nera jokio aprasymo</description>
|
<description lang="en_GB">nera jokio aprasymo</description>
|
||||||
@@ -18,6 +20,6 @@
|
|||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<fanart>fanart.jpg</fanart>
|
<fanart>fanart.jpg</fanart>
|
||||||
</assets>
|
</assets>
|
||||||
<news>jokiu naujienu..</news>
|
<news>0.0.25: sending Kodi's watched history to the server is a button in the settings. 0.0.24: watched state and resume points kept on the server, shared with other apps</news>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Kodi's own watched history of this add-on (its video database) to the server,
|
||||||
|
# as late reports: whatever the server knows that's newer wins. Started from the
|
||||||
|
# add-on settings, on the Kodi whose history counts (e.g. the main one).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
import xbmcvfs
|
||||||
|
|
||||||
|
from service import ADDON_ID, FILE_ID, is_ours, log, report
|
||||||
|
|
||||||
|
|
||||||
|
def newest_video_database():
|
||||||
|
folder = xbmcvfs.translatePath('special://database')
|
||||||
|
versions = [(int(m.group(1)), name) for name in os.listdir(folder)
|
||||||
|
for m in [re.match(r'MyVideos(\d+)\.db$', name)] if m]
|
||||||
|
return os.path.join(folder, max(versions)[1]) if versions else None
|
||||||
|
|
||||||
|
|
||||||
|
def to_ms(last_played):
|
||||||
|
try:
|
||||||
|
return int(time.mktime(datetime.strptime(last_played, '%Y-%m-%d %H:%M:%S').timetuple()) * 1000)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 1 # unknown: older than anything the server has
|
||||||
|
|
||||||
|
|
||||||
|
def read_history():
|
||||||
|
"""{file id: (play count, resume position, total time, last played in ms)} of this add-on's files."""
|
||||||
|
database = newest_video_database()
|
||||||
|
if database is None:
|
||||||
|
return {}
|
||||||
|
connection = sqlite3.connect('file:{0}?mode=ro'.format(database), uri=True)
|
||||||
|
try:
|
||||||
|
rows = connection.execute(
|
||||||
|
'SELECT path.strPath, files.strFilename, files.playCount, files.lastPlayed,'
|
||||||
|
' bookmark.timeInSeconds, bookmark.totalTimeInSeconds'
|
||||||
|
' FROM files JOIN path ON path.idPath = files.idPath'
|
||||||
|
' LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = 1'
|
||||||
|
).fetchall()
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
# the latest wins (a file can be there by plugin url and by stream url)
|
||||||
|
history = {}
|
||||||
|
for folder, filename, play_count, last_played, position, total in rows:
|
||||||
|
url = unquote(unquote((folder or '') + (filename or '')))
|
||||||
|
match = FILE_ID.search(url)
|
||||||
|
if match is None or not (url.startswith('plugin://' + ADDON_ID) or is_ours(url)):
|
||||||
|
continue
|
||||||
|
if not play_count and not position:
|
||||||
|
continue
|
||||||
|
file_id = int(match.group(1))
|
||||||
|
at = to_ms(last_played)
|
||||||
|
if file_id not in history or history[file_id][3] < at:
|
||||||
|
history[file_id] = (play_count or 0, position or 0, total or 0, at)
|
||||||
|
return history
|
||||||
|
|
||||||
|
|
||||||
|
def send_history(history, on_progress=None):
|
||||||
|
"""
|
||||||
|
How many files went through: stops at the first one that doesn't (server
|
||||||
|
unreachable), or when on_progress(files done) returns False.
|
||||||
|
"""
|
||||||
|
done = 0
|
||||||
|
for file_id, (play_count, position, total, at) in history.items():
|
||||||
|
if on_progress is not None and on_progress(done) is False:
|
||||||
|
break
|
||||||
|
sent = True
|
||||||
|
if play_count > 0:
|
||||||
|
sent = report(file_id, total, total, 'end', at)
|
||||||
|
if sent and position > 0:
|
||||||
|
# (also) a resume point, e.g. rewatching
|
||||||
|
sent = report(file_id, position, total, 'stop', at)
|
||||||
|
if not sent:
|
||||||
|
break
|
||||||
|
done += 1
|
||||||
|
log('sent watched history of {0} of {1} files'.format(done, len(history)))
|
||||||
|
return done
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#my_setting = my_addon.getSetting('my_setting') # returns the string 'true' or 'false'
|
#my_setting = my_addon.getSetting('my_setting') # returns the string 'true' or 'false'
|
||||||
#my_addon.setSetting('my_setting', 'false')
|
#my_addon.setSetting('my_setting', 'false')
|
||||||
|
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from urllib.parse import urlencode, parse_qsl
|
from urllib.parse import urlencode, parse_qsl
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
@@ -27,15 +28,14 @@ _url = sys.argv[0]
|
|||||||
# Get the plugin handle as an integer number.
|
# Get the plugin handle as an integer number.
|
||||||
_handle = int(sys.argv[1])
|
_handle = int(sys.argv[1])
|
||||||
|
|
||||||
# todo - get from settings
|
|
||||||
#API_URL = xbmcplugin.getSetting(_handle, 'api_url') #if xbmcplugin.getSetting(_handle, 'api_url') else xbmc.getSetting("api_url")
|
|
||||||
my_addon = xbmcaddon.Addon()
|
my_addon = xbmcaddon.Addon()
|
||||||
API_URL = my_addon.getSetting('api_url')
|
# the server, e.g. https://media.example.com: menus come from <server>/api, videos from the
|
||||||
VIDEO_URL = xbmcplugin.getSetting(_handle, 'video_url')
|
# server-relative paths it lists. Stored as "api_url", which used to end with /api (next to a
|
||||||
|
# separate video url of the same server); either way works.
|
||||||
|
SERVER_URL = re.sub(r'/api$', '', my_addon.getSetting('api_url').strip().rstrip('/'))
|
||||||
|
API_URL = SERVER_URL + '/api'
|
||||||
AUTH_KEY = my_addon.getSetting('auth_key')
|
AUTH_KEY = my_addon.getSetting('auth_key')
|
||||||
|
|
||||||
xbmcplugin.getSetting
|
|
||||||
|
|
||||||
def getHttpURL(url):
|
def getHttpURL(url):
|
||||||
|
|
||||||
request = urllib.request.Request(url)
|
request = urllib.request.Request(url)
|
||||||
@@ -183,6 +183,9 @@ def list_APIresponse(objAPIresponse):
|
|||||||
if len(contextMenus) > 0:
|
if len(contextMenus) > 0:
|
||||||
list_item.addContextMenuItems(listContextMenus)
|
list_item.addContextMenuItems(listContextMenus)
|
||||||
|
|
||||||
|
# watched / resume point as the server has it (shared with other apps)
|
||||||
|
set_watch_state(list_item, item, is_folder)
|
||||||
|
|
||||||
# Add our item to the Kodi virtual folder listing.
|
# Add our item to the Kodi virtual folder listing.
|
||||||
xbmcplugin.addDirectoryItem(_handle, url, list_item, is_folder)
|
xbmcplugin.addDirectoryItem(_handle, url, list_item, is_folder)
|
||||||
|
|
||||||
@@ -195,7 +198,47 @@ def list_APIresponse(objAPIresponse):
|
|||||||
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_DATE)
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_DATE)
|
||||||
|
|
||||||
# Finish creating a virtual folder.
|
# Finish creating a virtual folder.
|
||||||
xbmcplugin.endOfDirectory(_handle)
|
# not cached: watch state may have changed (on another device too) when coming back to it
|
||||||
|
xbmcplugin.endOfDirectory(_handle, cacheToDisc=False)
|
||||||
|
|
||||||
|
|
||||||
|
def set_watch_state(list_item, item, is_folder):
|
||||||
|
"""
|
||||||
|
Watched state and resume point from the server, which keeps them for all apps
|
||||||
|
(the service reports Kodi's playback there). Set explicitly, they take
|
||||||
|
precedence over Kodi's own database.
|
||||||
|
"""
|
||||||
|
if is_folder:
|
||||||
|
# shows and seasons: watched episodes out of the total
|
||||||
|
total = item.get('totalCount', 0)
|
||||||
|
if total > 0:
|
||||||
|
watched = item.get('watchedCount', 0)
|
||||||
|
list_item.setProperty('TotalEpisodes', str(total))
|
||||||
|
list_item.setProperty('WatchedEpisodes', str(watched))
|
||||||
|
list_item.setProperty('UnWatchedEpisodes', str(total - watched))
|
||||||
|
list_item.setInfo('video', {'playcount': 1 if watched >= total else 0})
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'playcount' not in item:
|
||||||
|
return
|
||||||
|
|
||||||
|
info = {'playcount': item['playcount']}
|
||||||
|
if 'lastplayed' in item:
|
||||||
|
info['lastplayed'] = item['lastplayed']
|
||||||
|
list_item.setInfo('video', info)
|
||||||
|
|
||||||
|
# no resume point = 0, which also clears one Kodi may have kept;
|
||||||
|
# Kodi only takes it with a total time
|
||||||
|
resume = item.get('resume', {})
|
||||||
|
position = resume.get('position', 0)
|
||||||
|
total = resume.get('total') or item.get('duration', 0)
|
||||||
|
tag = list_item.getVideoInfoTag()
|
||||||
|
if hasattr(tag, 'setResumePoint'):
|
||||||
|
# Kodi 20+
|
||||||
|
tag.setResumePoint(position, total)
|
||||||
|
else:
|
||||||
|
list_item.setProperty('ResumeTime', str(position))
|
||||||
|
list_item.setProperty('TotalTime', str(total))
|
||||||
|
|
||||||
def select_APIresponse(objAPIresponse):
|
def select_APIresponse(objAPIresponse):
|
||||||
|
|
||||||
@@ -234,7 +277,8 @@ def play_video(path):
|
|||||||
#xbmcgui.Dialog().ok("debug", "playvideo", path)
|
#xbmcgui.Dialog().ok("debug", "playvideo", path)
|
||||||
|
|
||||||
if 'http' not in path:
|
if 'http' not in path:
|
||||||
path = VIDEO_URL + path
|
# server-relative, e.g. /api?path=all/play/103
|
||||||
|
path = SERVER_URL + ('' if path.startswith('/') else '/') + path
|
||||||
|
|
||||||
# Create a playable item with a path to play.
|
# Create a playable item with a path to play.
|
||||||
play_item = xbmcgui.ListItem(path=path)
|
play_item = xbmcgui.ListItem(path=path)
|
||||||
@@ -294,6 +338,40 @@ def queryAPI(paramstring):
|
|||||||
|
|
||||||
#dialog.textviewer('Plot', 'Some movie plot.')
|
#dialog.textviewer('Plot', 'Some movie plot.')
|
||||||
|
|
||||||
|
def send_watched_history():
|
||||||
|
"""
|
||||||
|
This Kodi's own watched history of the add-on to the server, which keeps
|
||||||
|
whatever it has that's newer. Meant for the Kodi whose history counts.
|
||||||
|
"""
|
||||||
|
import history
|
||||||
|
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
files = history.read_history()
|
||||||
|
if not files:
|
||||||
|
dialog.ok("sklk", "This Kodi has no watched history of the add-on.")
|
||||||
|
return
|
||||||
|
if not dialog.yesno("sklk", "Send this Kodi's watched history ({0} files) to the server?\n"
|
||||||
|
"Newer state on the server stays.".format(len(files))):
|
||||||
|
return
|
||||||
|
|
||||||
|
progress = xbmcgui.DialogProgress()
|
||||||
|
progress.create("sklk", "Sending watched history...")
|
||||||
|
|
||||||
|
def on_progress(done):
|
||||||
|
progress.update(done * 100 // len(files))
|
||||||
|
return not progress.iscanceled()
|
||||||
|
|
||||||
|
try:
|
||||||
|
sent = history.send_history(files, on_progress)
|
||||||
|
finally:
|
||||||
|
progress.close()
|
||||||
|
|
||||||
|
if sent == len(files):
|
||||||
|
dialog.ok("sklk", "Sent the watched history of {0} files.".format(sent))
|
||||||
|
else:
|
||||||
|
dialog.ok("sklk", "Sent {0} of {1} files (stopped, or the server didn't answer).".format(sent, len(files)))
|
||||||
|
|
||||||
|
|
||||||
def router(paramstring):
|
def router(paramstring):
|
||||||
"""
|
"""
|
||||||
Router function that calls other functions
|
Router function that calls other functions
|
||||||
@@ -303,6 +381,11 @@ def router(paramstring):
|
|||||||
:type paramstring: str
|
:type paramstring: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not SERVER_URL:
|
||||||
|
xbmcgui.Dialog().ok("sklk", "Set the server URL in the add-on settings first.")
|
||||||
|
my_addon.openSettings()
|
||||||
|
return
|
||||||
|
|
||||||
# Parse a URL-encoded paramstring to the dictionary of
|
# Parse a URL-encoded paramstring to the dictionary of
|
||||||
# {<parameter>: <value>} elements
|
# {<parameter>: <value>} elements
|
||||||
params = dict(parse_qsl(paramstring))
|
params = dict(parse_qsl(paramstring))
|
||||||
@@ -315,6 +398,9 @@ def router(paramstring):
|
|||||||
elif params['action'] == 'play':
|
elif params['action'] == 'play':
|
||||||
# Play a video from a provided URL.
|
# Play a video from a provided URL.
|
||||||
play_video(params['path'])
|
play_video(params['path'])
|
||||||
|
elif params['action'] == 'send_history':
|
||||||
|
# button in the add-on settings
|
||||||
|
send_watched_history()
|
||||||
elif params['action'] == 'ignore':
|
elif params['action'] == 'ignore':
|
||||||
# do nothing, this is info item
|
# do nothing, this is info item
|
||||||
strVariable = "nothing"
|
strVariable = "nothing"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<setting id="api_url" type="text" default="http://sklk.lt:8181/api" label="API URL"/>
|
<!-- the server, e.g. https://media.example.com ("api_url" for older versions' value, which ended with /api) -->
|
||||||
<setting id="video_url" type="text" default="http://sklk.lt:8181" label="VIDEO URL"/>
|
<setting id="api_url" type="text" default="" label="Server URL"/>
|
||||||
<setting id="auth_key" type="text" default="" label="Auth key"/>
|
<setting id="auth_key" type="text" default="" label="Auth key"/>
|
||||||
|
<!-- on the Kodi whose watched history counts (e.g. the main one); the server keeps what's newer -->
|
||||||
|
<setting id="send_history" type="action" label="Send this Kodi's watched history to the server"
|
||||||
|
action="RunPlugin(plugin://plugin.video.sklk/?action=send_history)"/>
|
||||||
</settings>
|
</settings>
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Background service: reports playback of this add-on's files to the server
|
||||||
|
# (POST <server>/api/state/<file id>), which keeps watched state and resume points
|
||||||
|
# for every app (e.g. the SKLK TV app); listings come back with that state.
|
||||||
|
# (Kodi's own earlier history: see history.py.)
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
ADDON_ID = xbmcaddon.Addon().getAddonInfo('id')
|
||||||
|
FILE_ID = re.compile(r'all/play/(\d+)')
|
||||||
|
REPORT_EVERY = 30 # seconds, while playing
|
||||||
|
CLIENT = 'kodi'
|
||||||
|
|
||||||
|
|
||||||
|
def log(message, level=xbmc.LOGINFO):
|
||||||
|
xbmc.log('[{0} service] {1}'.format(ADDON_ID, message), level)
|
||||||
|
|
||||||
|
|
||||||
|
def server_url():
|
||||||
|
"""The server setting (see main.py), read each time since it may change."""
|
||||||
|
return re.sub(r'/api$', '', xbmcaddon.Addon().getSetting('api_url').strip().rstrip('/'))
|
||||||
|
|
||||||
|
|
||||||
|
def report(file_id, position, duration, event, at=None):
|
||||||
|
"""
|
||||||
|
Positions in seconds; at (ms since epoch) only for late reports.
|
||||||
|
True if the server got it (an answer that it doesn't know the file counts too).
|
||||||
|
"""
|
||||||
|
if not server_url():
|
||||||
|
return False
|
||||||
|
payload = {'position': position, 'event': event, 'client': CLIENT}
|
||||||
|
if duration:
|
||||||
|
payload['duration'] = duration
|
||||||
|
if at:
|
||||||
|
payload['at'] = at
|
||||||
|
request = urllib.request.Request(
|
||||||
|
server_url() + '/api/state/' + str(file_id),
|
||||||
|
data=json.dumps(payload).encode('utf-8'),
|
||||||
|
headers={'Content-Type': 'application/json', 'Authorization': xbmcaddon.Addon().getSetting('auth_key')},
|
||||||
|
method='POST',
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request, timeout=10) as response:
|
||||||
|
response.read()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log('report failed: {0}'.format(e), xbmc.LOGWARNING)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_ours(url):
|
||||||
|
"""Streams of this add-on's server (as opposed to other add-ons, or LRT streams)."""
|
||||||
|
server = server_url()
|
||||||
|
return bool(server) and url.startswith(server + '/')
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_if_ours():
|
||||||
|
# e.g. Continue watching moves on to the next episode
|
||||||
|
if xbmc.getInfoLabel('Container.FolderPath').startswith('plugin://' + ADDON_ID):
|
||||||
|
xbmc.executebuiltin('Container.Refresh')
|
||||||
|
|
||||||
|
|
||||||
|
class Player(xbmc.Player):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.file_id = None
|
||||||
|
self.position = 0
|
||||||
|
self.duration = 0
|
||||||
|
self.last_report = 0
|
||||||
|
# (file_id, position, duration, event, at): retried until it goes through
|
||||||
|
self.unsent = None
|
||||||
|
|
||||||
|
def onAVStarted(self):
|
||||||
|
self.finish('stop') # in case another file was playing
|
||||||
|
try:
|
||||||
|
url = self.getPlayingFile()
|
||||||
|
except RuntimeError:
|
||||||
|
return
|
||||||
|
match = FILE_ID.search(url) if is_ours(url) else None
|
||||||
|
if match is None:
|
||||||
|
return
|
||||||
|
self.file_id = int(match.group(1))
|
||||||
|
self.position = 0
|
||||||
|
self.duration = 0
|
||||||
|
self.update()
|
||||||
|
self.send('start')
|
||||||
|
|
||||||
|
def onPlayBackPaused(self):
|
||||||
|
if self.file_id is not None:
|
||||||
|
self.update()
|
||||||
|
self.send('pause')
|
||||||
|
|
||||||
|
def onPlayBackStopped(self):
|
||||||
|
self.finish('stop')
|
||||||
|
|
||||||
|
def onPlayBackEnded(self):
|
||||||
|
self.finish('end')
|
||||||
|
|
||||||
|
def onPlayBackError(self):
|
||||||
|
self.finish('stop')
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
"""Every second, from the service loop."""
|
||||||
|
if self.unsent and time.time() - self.last_report >= REPORT_EVERY:
|
||||||
|
self.last_report = time.time()
|
||||||
|
if report(*self.unsent):
|
||||||
|
self.unsent = None
|
||||||
|
|
||||||
|
if self.file_id is None:
|
||||||
|
return
|
||||||
|
self.update()
|
||||||
|
if time.time() - self.last_report >= REPORT_EVERY and not xbmc.getCondVisibility('Player.Paused'):
|
||||||
|
self.send('tick')
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# after a stop there's no time to ask for, the last one known is reported
|
||||||
|
try:
|
||||||
|
if self.isPlayingVideo():
|
||||||
|
self.position = self.getTime()
|
||||||
|
self.duration = self.getTotalTime() or self.duration
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finish(self, event):
|
||||||
|
if self.file_id is None:
|
||||||
|
return
|
||||||
|
if event == 'end':
|
||||||
|
self.position = self.duration
|
||||||
|
self.send(event)
|
||||||
|
self.file_id = None
|
||||||
|
refresh_if_ours()
|
||||||
|
|
||||||
|
def send(self, event):
|
||||||
|
self.last_report = time.time()
|
||||||
|
args = (self.file_id, self.position, self.duration, event)
|
||||||
|
if report(*args):
|
||||||
|
if self.unsent and self.unsent[0] == self.file_id:
|
||||||
|
self.unsent = None
|
||||||
|
else:
|
||||||
|
self.unsent = args + (int(time.time() * 1000),)
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
monitor = xbmc.Monitor()
|
||||||
|
player = Player()
|
||||||
|
while not monitor.waitForAbort(1):
|
||||||
|
player.tick()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
||||||
Reference in new issue
Block a user