mirror of
https://github.com/Screenly/Anthias.git
synced 2025-12-23 22:38:05 -05:00
fix(viewer): make asset navigation instantaneous (#2539)
This commit is contained in:
@@ -79,18 +79,6 @@ class TestLoadBrowser(ViewerTestCase):
|
||||
self.m_cmd.assert_called_once_with('ScreenlyWebview')
|
||||
|
||||
|
||||
class TestSignalHandlers(ViewerTestCase):
|
||||
@mock.patch('vlc.Instance', mock.MagicMock())
|
||||
@mock.patch(
|
||||
'viewer.media_player.get_device_type',
|
||||
return_value='pi4'
|
||||
)
|
||||
def test_usr1(self, lookup_mock):
|
||||
self.p_killall.start()
|
||||
self.assertEqual(None, self.u.sigusr1(None, None))
|
||||
self.p_killall.stop()
|
||||
|
||||
|
||||
class TestWatchdog(ViewerTestCase):
|
||||
def test_watchdog_should_create_file_if_not_exists(self):
|
||||
try:
|
||||
|
||||
@@ -7,7 +7,7 @@ import logging
|
||||
import sys
|
||||
from builtins import range
|
||||
from os import getenv, path
|
||||
from signal import SIGALRM, SIGUSR1, signal
|
||||
from signal import SIGALRM, signal
|
||||
from time import sleep
|
||||
|
||||
import django
|
||||
@@ -31,8 +31,8 @@ from viewer.media_player import MediaPlayerProxy
|
||||
from viewer.playback import navigate_to_asset, play_loop, skip_asset, stop_loop
|
||||
from viewer.utils import (
|
||||
command_not_found,
|
||||
get_skip_event,
|
||||
sigalrm,
|
||||
sigusr1,
|
||||
wait_for_server,
|
||||
watchdog,
|
||||
)
|
||||
@@ -196,9 +196,13 @@ def view_video(uri, duration):
|
||||
view_image('null')
|
||||
|
||||
try:
|
||||
while media_player.is_playing():
|
||||
watchdog()
|
||||
sleep(1)
|
||||
skip_event = get_skip_event()
|
||||
skip_event.clear()
|
||||
if skip_event.wait(timeout=int(duration)):
|
||||
logging.info('Skip detected during video playback, stopping video')
|
||||
media_player.stop()
|
||||
else:
|
||||
pass
|
||||
except sh.ErrorReturnCode_1:
|
||||
logging.info(
|
||||
'Resource URI is not correct, remote host is not responding or '
|
||||
@@ -225,7 +229,15 @@ def asset_loop(scheduler):
|
||||
logging.info(
|
||||
'Playlist is empty. Sleeping for %s seconds', EMPTY_PL_DELAY)
|
||||
view_image(STANDBY_SCREEN)
|
||||
sleep(EMPTY_PL_DELAY)
|
||||
skip_event = get_skip_event()
|
||||
skip_event.clear()
|
||||
if skip_event.wait(timeout=EMPTY_PL_DELAY):
|
||||
# Skip was triggered, continue immediately to next iteration
|
||||
logging.info(
|
||||
'Skip detected during empty playlist wait, continuing')
|
||||
else:
|
||||
# Duration elapsed normally, continue to next iteration
|
||||
pass
|
||||
|
||||
elif (
|
||||
path.isfile(asset['uri']) or
|
||||
@@ -248,12 +260,27 @@ def asset_loop(scheduler):
|
||||
if 'image' in mime or 'web' in mime:
|
||||
duration = int(asset['duration'])
|
||||
logging.info('Sleeping for %s', duration)
|
||||
sleep(duration)
|
||||
skip_event = get_skip_event()
|
||||
skip_event.clear()
|
||||
if skip_event.wait(timeout=duration):
|
||||
# Skip was triggered, continue immediately to next iteration
|
||||
logging.info('Skip detected, moving to next asset immediately')
|
||||
else:
|
||||
# Duration elapsed normally, continue to next asset
|
||||
pass
|
||||
|
||||
else:
|
||||
logging.info('Asset %s at %s is not available, skipping.',
|
||||
asset['name'], asset['uri'])
|
||||
sleep(0.5)
|
||||
skip_event = get_skip_event()
|
||||
skip_event.clear()
|
||||
if skip_event.wait(timeout=0.5):
|
||||
# Skip was triggered, continue immediately to next iteration
|
||||
logging.info(
|
||||
'Skip detected during asset unavailability wait, continuing')
|
||||
else:
|
||||
# Duration elapsed normally, continue to next iteration
|
||||
pass
|
||||
|
||||
|
||||
def setup():
|
||||
@@ -266,7 +293,7 @@ def setup():
|
||||
# or we can create a new class that extends Exception.
|
||||
sys.exit(1)
|
||||
|
||||
signal(SIGUSR1, sigusr1)
|
||||
# Skip event is now handled via threading instead of signals
|
||||
signal(SIGALRM, sigalrm)
|
||||
|
||||
load_settings()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sh
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
import vlc
|
||||
|
||||
from lib.device_helper import get_device_type
|
||||
@@ -29,31 +31,30 @@ class MediaPlayer():
|
||||
class FFMPEGMediaPlayer(MediaPlayer):
|
||||
def __init__(self):
|
||||
MediaPlayer.__init__(self)
|
||||
self.run = None
|
||||
self.player_args = list()
|
||||
self.player_kwargs = dict()
|
||||
self.process = None
|
||||
|
||||
def set_asset(self, uri, duration):
|
||||
self.player_args = ['ffplay', uri, '-autoexit']
|
||||
self.player_kwargs = {
|
||||
'_bg': True,
|
||||
'_ok_code': [0, 124],
|
||||
}
|
||||
self.uri = uri
|
||||
|
||||
def play(self):
|
||||
self.run = sh.Command(self.player_args[0])(
|
||||
*self.player_args[1:], **self.player_kwargs
|
||||
self.process = subprocess.Popen(
|
||||
['ffplay', '-autoexit', self.uri],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
if self.run:
|
||||
self.run.kill()
|
||||
except OSError:
|
||||
pass
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
self.process = None
|
||||
except Exception as e:
|
||||
logging.error(f'Exception in stop(): {e}')
|
||||
|
||||
def is_playing(self):
|
||||
return bool(self.run.process.alive)
|
||||
if self.process:
|
||||
return self.process.poll() is None
|
||||
return False
|
||||
|
||||
|
||||
class VLCMediaPlayer(MediaPlayer):
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
from os import system
|
||||
import threading
|
||||
|
||||
# Global event for instant asset switching
|
||||
skip_event = threading.Event()
|
||||
|
||||
|
||||
def skip_asset(scheduler, back=False):
|
||||
if back is True:
|
||||
scheduler.reverse = True
|
||||
system('pkill -SIGUSR1 -f viewer')
|
||||
skip_event.set()
|
||||
|
||||
|
||||
def navigate_to_asset(scheduler, asset_id):
|
||||
scheduler.extra_asset = asset_id
|
||||
system('pkill -SIGUSR1 -f viewer')
|
||||
skip_event.set()
|
||||
|
||||
|
||||
def stop_loop(scheduler):
|
||||
|
||||
@@ -6,7 +6,6 @@ import requests
|
||||
|
||||
from lib.errors import SigalrmError
|
||||
from settings import LISTEN, PORT
|
||||
from viewer.media_player import MediaPlayerProxy
|
||||
|
||||
WATCHDOG_PATH = '/tmp/screenly.watchdog'
|
||||
|
||||
@@ -18,13 +17,12 @@ def sigalrm(signum, frame):
|
||||
raise SigalrmError("SigalrmError")
|
||||
|
||||
|
||||
def sigusr1(signum, frame):
|
||||
def get_skip_event():
|
||||
"""
|
||||
The signal interrupts sleep() calls, so the currently
|
||||
playing web or image asset is skipped.
|
||||
Get the global skip event for instant asset switching.
|
||||
"""
|
||||
logging.info('USR1 received, skipping.')
|
||||
MediaPlayerProxy.get_instance().stop()
|
||||
from viewer.playback import skip_event
|
||||
return skip_event
|
||||
|
||||
|
||||
def command_not_found():
|
||||
|
||||
Reference in New Issue
Block a user