Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve resumepoints #652

Merged
merged 2 commits into from
Dec 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ msgid "Could not retrieve this program list. VRT Search API url is too long. Unf
msgstr ""

msgctxt "#30975"
msgid "Failed to get favorites token from VRT NU"
msgid "Failed to get user token from VRT NU"
msgstr ""

msgctxt "#30976"
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.nl_nl/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -875,8 +875,8 @@ msgid "Could not retrieve this program list. VRT Search API url is too long. Unf
msgstr "Deze programmalijst kan niet opgehaald worden. VRT Search API url is te lang. Enkele favoriete programma's vergeten zal de lengte van de URL verminderen en dit probleem mogelijk oplossen."

msgctxt "#30975"
msgid "Failed to get favorites token from VRT NU"
msgstr "Ophalen van het favorieten token van VRT NU is mislukt"
msgid "Failed to get user token from VRT NU"
msgstr "Ophalen van het gebruikerstoken van VRT NU is mislukt"

msgctxt "#30976"
msgid "Failed to (un)follow program '{program}' at VRT NU"
Expand Down
19 changes: 19 additions & 0 deletions resources/lib/kodiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
prop_dict = dict(
IsInternetStream='true' if is_playable else 'false',
IsPlayable='true' if is_playable else 'false',
IsFolder='false' if is_folder else 'true',
)
if title_item.prop_dict:
title_item.prop_dict.update(prop_dict)
Expand Down Expand Up @@ -729,6 +730,24 @@ def end_of_directory():
xbmcplugin.endOfDirectory(handle=plugin.handle, succeeded=False, updateListing=False, cacheToDisc=False)


def wait_for_resumepoints():
"""Wait for resumepoints to be updated"""
update = get_property('vrtnu_resumepoints')
if update == 'busy':
import time
timeout = time.time() + 5 # 5 seconds timeout
log(3, 'Resumepoint update is busy, wait')
while update != 'ready':
if time.time() > timeout: # Exit loop in case something goes wrong
break
xbmc.sleep(50)
update = get_property('vrtnu_resumepoints')
set_property('vrtnu_resumepoints', None)
log(3, 'Resumepoint update is ready, continue')
return True
return False


def log(level=1, message='', **kwargs):
"""Log info messages to Kodi"""
debug_logging = get_global_setting('debug.showloginfo') # Returns a boolean
Expand Down
87 changes: 28 additions & 59 deletions resources/lib/playerinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from xbmc import getInfoLabel, Player, PlayList

from apihelper import ApiHelper
from data import SECONDS_MARGIN, CHANNELS
from data import CHANNELS
from favorites import Favorites
from kodiutils import addon_id, container_reload, get_advanced_setting_int, get_setting, has_addon, log, notify
from kodiutils import addon_id, get_setting, has_addon, log, notify, set_property
from resumepoints import ResumePoints
from utils import assetpath_to_id, play_url_to_id, to_unicode, url_to_episode

Expand Down Expand Up @@ -50,6 +50,10 @@ def onPlayBackStarted(self): # pylint: disable=invalid-name

log(3, '[PlayerInfo %d] Event onPlayBackStarted' % self.thread_id)

# Update previous episode when using "Up Next"
if self.path.startswith('plugin://plugin.video.vrt.nu/play/upnext'):
self.push_position(position=self.last_pos, total=self.total)

# Reset episode data
self.asset_id = None
self.title = None
Expand All @@ -60,11 +64,12 @@ def onPlayBackStarted(self): # pylint: disable=invalid-name

# Avoid setting resumepoints for livestreams
for channel in CHANNELS:
if ep_id.get('video_id') == channel.get('live_stream_id'):
if ep_id.get('video_id') and ep_id.get('video_id') == channel.get('live_stream_id'):
log(3, '[PlayerInfo %d] Avoid setting resumepoints for livestream %s' % (self.thread_id, ep_id.get('video_id')))
self.listen = False
return

# Get episode data needed to update resumepoints
# Get episode data needed to update resumepoints from VRT NU Search API
episode = self.apihelper.get_single_episode_data(video_id=ep_id.get('video_id'), whatson_id=ep_id.get('whatson_id'), video_url=ep_id.get('video_url'))

# Avoid setting resumepoints without episode data
Expand All @@ -84,7 +89,6 @@ def onAVStarted(self): # pylint: disable=invalid-name
self.quit.clear()
self.update_position()
self.update_total()
self.push_position(position=self.last_pos, total=self.total) # Update position at start so resumepoint gets deleted
self.push_upnext()

# StreamPosition thread keeps running when watching multiple episode with "Up Next"
Expand All @@ -103,11 +107,16 @@ def onPlayBackSeek(self, time, seekOffset): # pylint: disable=invalid-name
log(3, '[PlayerInfo %d] Event onPlayBackSeek time=%d offset=%d' % (self.thread_id, time, seekOffset))
self.last_pos = time // 1000

# If we seek beyond the end, quit Player
# If we seek beyond the start or end, set property to let wait_for_resumepoints function know that update resume is busy
if self.last_pos >= self.total:
set_property('vrtnu_resumepoints', 'busy')
# Exit Player faster
self.quit.set()
self.stop()

if self.last_pos < 0:
set_property('vrtnu_resumepoints', 'busy')

def onPlayBackPaused(self): # pylint: disable=invalid-name
"""Called when user pauses a playing file"""
if not self.listen:
Expand All @@ -123,17 +132,15 @@ def onPlayBackResumed(self): # pylint: disable=invalid-name
return
suffix = 'after pausing' if self.paused else 'after playlist change'
log(3, '[PlayerInfo %d] Event onPlayBackResumed %s' % (self.thread_id, suffix))
if not self.paused:
self.push_position(position=self.last_pos, total=self.total)
self.paused = False

def onPlayBackEnded(self): # pylint: disable=invalid-name
"""Called when Kodi has ended playing a file"""
if not self.listen:
return
self.last_pos = self.total
self.quit.set()
log(3, '[PlayerInfo %d] Event onPlayBackEnded' % self.thread_id)
self.last_pos = self.total

def onPlayBackError(self): # pylint: disable=invalid-name
"""Called when playback stops due to an error"""
Expand All @@ -149,9 +156,9 @@ def onPlayBackStopped(self): # pylint: disable=invalid-name
self.quit.set()
log(3, '[PlayerInfo %d] Event onPlayBackStopped' % self.thread_id)

def onThreadExit(self): # pylint: disable=invalid-name
"""Called when player stops, before the player exited, so before the menu refresh"""
log(3, '[PlayerInfo %d] Event onThreadExit' % self.thread_id)
def onPlayerExit(self): # pylint: disable=invalid-name
"""Called when player exits"""
log(3, '[PlayerInfo %d] Event onPlayerExit' % self.thread_id)
self.positionthread = None
self.push_position(position=self.last_pos, total=self.total)

Expand All @@ -161,7 +168,7 @@ def stream_position(self):
self.update_position()
if self.quit.wait(timeout=0.2):
break
self.onThreadExit()
self.onPlayerExit()

def add_upnext(self, video_id):
"""Add Up Next url to Kodi Player"""
Expand Down Expand Up @@ -210,62 +217,24 @@ def update_total(self):
except RuntimeError:
pass

def push_position(self, position=0, total=100):
"""Push player position to VRT NU resumepoints API"""
def push_position(self, position, total):
"""Push player position to VRT NU resumepoints API and reload container"""
# Not all content has an asset_id
if not self.asset_id:
return

# Set property to let wait_for_resumepoints function know that update resume is busy
set_property('vrtnu_resumepoints', 'busy')

# Push resumepoint to VRT NU
self.resumepoints.update(
asset_id=self.asset_id,
title=self.title,
url=self.url,
position=position,
total=total,
whatson_id=self.whatson_id,
asynchronous=True
whatson_id=self.whatson_id
)

# Kodi internal watch status is only updated when the play action is initiated from the GUI, so this doesn't work after quitting "Up Next"
if (not self.path.startswith('plugin://plugin.video.vrt.nu/play/upnext')
and not self.overrule_kodi_watchstatus(position, total)):
return

# Do not reload container when playing or not stopped
if self.isPlaying() or not self.quit.is_set():
return

container_reload()

@staticmethod
def overrule_kodi_watchstatus(position, total):
"""Determine if we need to overrule the Kodi watch status"""

# Kodi uses different resumepoint margins than VRT NU, to obey to VRT NU resumepoint margins
# we sometimes need to overrule Kodi watch status.
# Use setting from advancedsettings.xml or default value
# https://github.com/xbmc/xbmc/blob/master/xbmc/settings/AdvancedSettings.cpp
# https://kodi.wiki/view/HOW-TO:Modify_automatic_watch_and_resume_points

ignoresecondsatstart = get_advanced_setting_int('video/ignoresecondsatstart', default=180)
ignorepercentatend = get_advanced_setting_int('video/ignorepercentatend', default=8)

# Convert percentage to seconds
ignoresecondsatend = round(total * (100 - ignorepercentatend) / 100.0)

if position <= max(SECONDS_MARGIN, ignoresecondsatstart):
# Check start margins
if SECONDS_MARGIN <= position <= ignoresecondsatstart:
return True
if ignoresecondsatstart <= position <= SECONDS_MARGIN:
return True

if position >= min(total - SECONDS_MARGIN, ignoresecondsatend):
# Check end margins
if total - SECONDS_MARGIN <= position <= ignoresecondsatend:
return True
if ignoresecondsatend <= position <= total - SECONDS_MARGIN:
return True

return False
# Set property to let wait_for_resumepoints function know that update resume is done
set_property('vrtnu_resumepoints', 'ready')
Loading