[media.vlc] Better state management.

This commit is contained in:
Fabio Manganiello 2024-10-13 16:10:09 +02:00
parent 0ab160569a
commit 0aae905754
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774

View file

@ -54,6 +54,8 @@ class MediaVlcPlugin(MediaPlugin):
self._on_stop_event = threading.Event() self._on_stop_event = threading.Event()
self._stop_lock = threading.RLock() self._stop_lock = threading.RLock()
self._latest_resource: Optional[MediaResource] = None self._latest_resource: Optional[MediaResource] = None
self._playing_url: Optional[str] = None
self._latest_player_state: PlayerState = PlayerState.STOP
@classmethod @classmethod
def _watched_event_types(cls): def _watched_event_types(cls):
@ -84,8 +86,9 @@ class MediaVlcPlugin(MediaPlugin):
] ]
def _init_vlc(self, resource: MediaResource, cache_streams: bool): def _init_vlc(self, resource: MediaResource, cache_streams: bool):
if self._instance: if self._player:
self.logger.info('Another instance is running, waiting for it to terminate') self.logger.info('Releasing previous VLC player instance')
self._close_player()
self._on_stop_event.wait() self._on_stop_event.wait()
for k, v in self._env.items(): for k, v in self._env.items():
@ -131,7 +134,7 @@ class MediaVlcPlugin(MediaPlugin):
if self._latest_resource: if self._latest_resource:
self.logger.debug('Closing latest resource') self.logger.debug('Closing latest resource')
self._latest_resource.close() self._latest_resource.close()
self._latest_resource = None self._playing_url = None
def _close_player(self): def _close_player(self):
if self._player: if self._player:
@ -192,16 +195,33 @@ class MediaVlcPlugin(MediaPlugin):
from vlc import EventType from vlc import EventType
self.logger.debug('Received VLC event: %s', event.type) self.logger.debug('Received VLC event: %s', event.type)
if event.type == EventType.MediaPlayerPlaying: # type: ignore playing_url = None
if self._player:
media = self._player.get_media()
playing_url = media.get_mrl() if media else None
new_state = self._player_state
old_state = self._latest_player_state
if event.type == EventType.MediaPlayerOpening or playing_url != self._playing_url: # type: ignore
self._post_event(
NewPlayingMediaEvent, resource=self._get_current_resource()
)
elif event.type == EventType.MediaPlayerPlaying or ( # type: ignore
new_state == PlayerState.PLAY and new_state != old_state
):
self._on_stop_event.clear() self._on_stop_event.clear()
self._post_event(MediaPlayEvent, resource=self._get_current_resource()) self._post_event(MediaPlayEvent, resource=self._get_current_resource())
elif event.type == EventType.MediaPlayerPaused: # type: ignore elif event.type == EventType.MediaPlayerPaused or ( # type: ignore
new_state == PlayerState.PAUSE and new_state != old_state
):
self._on_stop_event.clear() self._on_stop_event.clear()
self._post_event(MediaPauseEvent) self._post_event(MediaPauseEvent)
elif event.type in ( elif event.type in (
EventType.MediaPlayerStopped, # type: ignore EventType.MediaPlayerStopped, # type: ignore
EventType.MediaPlayerEndReached, # type: ignore EventType.MediaPlayerEndReached, # type: ignore
): ) or (new_state == PlayerState.STOP and new_state != old_state):
self._on_stop_event.set() self._on_stop_event.set()
self._post_event(MediaStopEvent) self._post_event(MediaStopEvent)
self._reset_state() self._reset_state()
@ -212,13 +232,13 @@ class MediaVlcPlugin(MediaPlugin):
EventType.MediaPlayerMediaChanged, # type: ignore EventType.MediaPlayerMediaChanged, # type: ignore
) )
): ):
if event.type == EventType.MediaPlayerMediaChanged: # type: ignore
self._post_event(NewPlayingMediaEvent, resource=self._title)
elif event.type == EventType.MediaPlayerLengthChanged: # type: ignore
self._post_event( self._post_event(
NewPlayingMediaEvent, resource=self._get_current_resource() NewPlayingMediaEvent, resource=self._get_current_resource()
) )
elif self._player and event.type == EventType.MediaPlayerTimeChanged: # type: ignore elif self._player and event.type in {
EventType.MediaPlayerLengthChanged, # type: ignore
EventType.MediaPlayerTimeChanged, # type: ignore
}:
pos = float(self._player.get_time() / 1000) pos = float(self._player.get_time() / 1000)
if self._latest_seek is None or abs(pos - self._latest_seek) > 5: if self._latest_seek is None or abs(pos - self._latest_seek) > 5:
self._post_event(MediaSeekEvent, position=pos) self._post_event(MediaSeekEvent, position=pos)
@ -235,6 +255,9 @@ class MediaVlcPlugin(MediaPlugin):
self.logger.error('VLC media player encountered an error') self.logger.error('VLC media player encountered an error')
self._reset_state() self._reset_state()
self._playing_url = playing_url
self._latest_player_state = new_state
return callback return callback
@action @action
@ -270,10 +293,9 @@ class MediaVlcPlugin(MediaPlugin):
cache_streams = ( cache_streams = (
cache_streams if cache_streams is not None else self.cache_streams cache_streams if cache_streams is not None else self.cache_streams
) )
media = self._get_resource(resource, metadata=metadata) self._latest_resource = self._get_resource(resource, metadata=metadata)
self._latest_resource = media self._init_vlc(self._latest_resource, cache_streams=cache_streams)
self.quit()
self._init_vlc(media, cache_streams=cache_streams)
if subtitles and self._player: if subtitles and self._player:
if subtitles.startswith('file://'): if subtitles.startswith('file://'):
subtitles = subtitles[len('file://') :] subtitles = subtitles[len('file://') :]
@ -282,14 +304,13 @@ class MediaVlcPlugin(MediaPlugin):
if self._player: if self._player:
self._player.play() self._player.play()
if self.volume:
self.set_volume(volume=self.volume)
if fullscreen or self._default_fullscreen: if fullscreen or self._default_fullscreen:
self.set_fullscreen(True) self.set_fullscreen(True)
if volume is not None or self._default_volume is not None: if volume is not None or self._default_volume is not None:
self.set_volume(volume if volume is not None else self._default_volume) self.set_volume(volume if volume is not None else self._default_volume)
elif self.volume is not None:
self.set_volume(volume=self.volume)
return self.status() return self.status()
@ -487,22 +508,16 @@ class MediaVlcPlugin(MediaPlugin):
} }
""" """
import vlc
with self._stop_lock: with self._stop_lock:
if not (self._player and self._latest_resource): if not (self._player and self._playing_url):
return {'state': PlayerState.STOP.value} return {'state': PlayerState.STOP.value}
status = self._latest_resource.to_dict() status: dict = (
vlc_state = self._player.get_state() self._latest_resource.to_dict()
if self._latest_resource
if vlc_state == vlc.State.Playing: # type: ignore else {'url': self._playing_url}
status['state'] = PlayerState.PLAY.value )
elif vlc_state == vlc.State.Paused: # type: ignore status['state'] = self._player_state.value
status['state'] = PlayerState.PAUSE.value
else:
status['state'] = PlayerState.STOP.value
status['position'] = ( status['position'] = (
float(self._player.get_time() / 1000) float(self._player.get_time() / 1000)
if self._player.get_time() is not None if self._player.get_time() is not None
@ -525,12 +540,31 @@ class MediaVlcPlugin(MediaPlugin):
status['path'] = status['url'] status['path'] = status['url']
status['pause'] = status['state'] == PlayerState.PAUSE.value status['pause'] = status['state'] == PlayerState.PAUSE.value
status['percent_pos'] = self._player.get_position() * 100 status['percent_pos'] = self._player.get_position() * 100
status['filename'] = self._latest_resource.filename status['filename'] = (
self._latest_resource.filename
if self._latest_resource
else self._playing_url
)
status['title'] = self._title status['title'] = self._title
status['volume'] = self._player.audio_get_volume() status['volume'] = self._player.audio_get_volume()
status['volume_max'] = 100 status['volume_max'] = 100
return status return status
@property
def _player_state(self) -> PlayerState:
import vlc
if not self._player:
return PlayerState.STOP
vlc_state = self._player.get_state()
if vlc_state == vlc.State.Playing: # type: ignore
return PlayerState.PLAY
elif vlc_state == vlc.State.Paused: # type: ignore
return PlayerState.PAUSE
return PlayerState.STOP
def on_stop(self, callback): def on_stop(self, callback):
self._on_stop_callbacks.append(callback) self._on_stop_callbacks.append(callback)