Skip to content

Commit

Permalink
feat: add video loading status info
Browse files Browse the repository at this point in the history
  • Loading branch information
vzhd1701 committed Jun 21, 2022
1 parent e156d1a commit 4c14188
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 181 deletions.
42 changes: 24 additions & 18 deletions gridplayer/utils/url_resolve/url_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,44 @@
class VideoURLResolverWorker(QObject):
url_resolved = pyqtSignal(ResolvedVideo)
error = pyqtSignal()
update_status = pyqtSignal(str)

def resolve(self, url):
try:
self.url_resolved.emit(resolve_url(url))
self.url_resolved.emit(self.resolve_url(url))
except BadURLException as e:
self.update_status.emit("Failed to resolve URL")
logger.error(e)
self.error.emit()
except Exception:
# log traceback stack
self.update_status.emit("Failed to resolve URL [Exception]")
logger.critical(traceback.format_exc())
self.error.emit()

def resolve_url(self, url: VideoURL) -> Optional[ResolvedVideo]:
url_resolvers = {
"streamlink": resolve_streamlink,
"yt-dlp": resolve_youtube_dl,
}

if YOUTUBE_MATCH.match(url):
url_resolvers = {"yt-dlp": resolve_youtube_dl}

for resolver_name, resolver in url_resolvers.items():
self.update_status.emit(f"Resolving URL via {resolver_name}")

logger.debug(f"Trying to resolve URL with {resolver.__name__}")
with contextlib.suppress(NoResolverPlugin):
return resolver(url)

return _resolve_generic(url)


class VideoURLResolver(QObject):
url_resolved = pyqtSignal(ResolvedVideo)
error = pyqtSignal()
update_status = pyqtSignal(str)

_resolve_url = pyqtSignal(str)

Expand All @@ -52,6 +74,7 @@ def __init__(self, **kwargs):

self.worker.url_resolved.connect(self.url_resolved)
self.worker.error.connect(self.error)
self.worker.update_status.connect(self.update_status)

self._resolve_url.connect(self.worker.resolve)

Expand All @@ -65,23 +88,6 @@ def resolve(self, url):
self._resolve_url.emit(url)


def resolve_url(url: VideoURL) -> Optional[ResolvedVideo]:
if YOUTUBE_MATCH.match(url):
return resolve_youtube_dl(url)

url_resolvers = [
resolve_streamlink,
resolve_youtube_dl,
]

for resolver in url_resolvers:
logger.debug(f"Trying to resolve URL with {resolver.__name__}")
with contextlib.suppress(NoResolverPlugin):
return resolver(url)

return _resolve_generic(url)


def _resolve_generic(url: VideoURL) -> Optional[ResolvedVideo]:
try:
is_live = is_http_live_stream(url)
Expand Down
24 changes: 18 additions & 6 deletions gridplayer/vlc_player/player_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,16 @@ def init_event_manager(self):
"encountered_error": self.cb_error,
"time_changed": self.cb_time_changed,
"media_parsed_changed": self.cb_parse_changed,
"buffering": self.cb_buffering,
}

for event_name, callback in callbacks.items():
self._event_manager.subscribe(event_name, callback)

def cb_buffering(self, event):
buffered_percent = int(event.u.new_cache)
self.notify_update_status("Buffering", buffered_percent)

def cb_playing(self, event):
self._log.debug("Media playing")

Expand All @@ -99,7 +104,7 @@ def cb_playing(self, event):
self._event_waiter.async_wait_for(
event="buffering",
on_completed=self._timer_unpause_failsafe.start,
on_timeout=self.notify_error,
on_timeout=lambda: self.error("Buffering timeout"),
timeout=INIT_TIMEOUT,
)

Expand Down Expand Up @@ -151,8 +156,7 @@ def cb_end_reached(self, event):
return

def cb_error(self, event):
self._log.error("MediaPlayer encountered an error")
self.notify_error()
self.error("Player error")

def cb_parse_changed(self, event):
self._log.debug("Media parse changed")
Expand Down Expand Up @@ -221,8 +225,13 @@ def cleanup(self):

def error(self, message):
self._log.error(message)
self.notify_update_status(message)
self.notify_error()

@abstractmethod
def notify_update_status(self, status, percent=0):
...

@abstractmethod
def notify_error(self):
...
Expand Down Expand Up @@ -288,10 +297,10 @@ def load_video(self, media_input: MediaInput):

self._media.add_options(*self._media_options)

self._log.debug("Parsing media")

self._media.parse_with_options(parse_flag, parse_timeout)

self.notify_update_status("Parsing media")

def load_video_st2_set_parsed_media(self):
"""Step 2. Start video player with parsed file"""

Expand All @@ -302,7 +311,7 @@ def load_video_st2_set_parsed_media(self):
self._event_waiter.async_wait_for(
event="buffering",
on_completed=self.loopback_load_video_st3_extract_media_track,
on_timeout=self.notify_error,
on_timeout=lambda: self.error("Buffering timeout"),
timeout=INIT_TIMEOUT,
)

Expand All @@ -311,6 +320,9 @@ def load_video_st2_set_parsed_media(self):
def load_video_st3_extract_media_track(self):
"""Step 3. Extract media track"""

if self._media_track_extract_attempts == 0:
self.notify_update_status("Preparing video output")

self._log.debug("Extracting media track")

self._init_media_track()
Expand Down
3 changes: 3 additions & 0 deletions gridplayer/vlc_player/player_base_threaded.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ class VlcPlayerThreaded(CommandLoopThreaded, VlcPlayerBase):
def start(self):
self.cmd_loop_start_thread(self.init_player)

def notify_update_status(self, status, percent=0):
self.cmd_send("update_status_emit", status, percent)

def notify_error(self):
self.cmd_send("error_state")

Expand Down
4 changes: 4 additions & 0 deletions gridplayer/vlc_player/video_driver_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class VLCVideoDriver(QObject, metaclass=QABC):

error = pyqtSignal()
crash = pyqtSignal(str)
update_status = pyqtSignal(str, int)

def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down Expand Up @@ -75,3 +76,6 @@ def audio_set_volume(self, volume):

def error_state(self):
self.error.emit()

def update_status_emit(self, status, percent):
self.update_status.emit(status, percent)
25 changes: 15 additions & 10 deletions gridplayer/widgets/video_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
from gridplayer.utils.url_resolve.static import ResolvedVideo
from gridplayer.utils.url_resolve.url_resolve import VideoURLResolver
from gridplayer.vlc_player.static import MediaInput
from gridplayer.widgets.video_block_status import StatusLabel
from gridplayer.widgets.video_frame_vlc_base import VideoFrameVLC
from gridplayer.widgets.video_overlay import OverlayBlock, OverlayBlockFloating
from gridplayer.widgets.video_status import VideoStatus

IN_PROGRESS_THRESHOLD_MS = 500

Expand Down Expand Up @@ -169,7 +169,7 @@ def __init__(self, video_driver, **kwargs):

self.ui_setup()

self.status_label.show()
self.video_status.show()
self.overlay.raise_()
self.overlay.hide()

Expand All @@ -183,6 +183,7 @@ def init_video_driver(self) -> VideoFrameVLC:
(video_driver.playback_status_changed, self.playback_status_changed),
(video_driver.error, self.video_driver_error),
(video_driver.crash, self.crash),
(video_driver.update_status, self.update_status),
(self.load_video, video_driver.load_video),
)

Expand All @@ -208,6 +209,7 @@ def init_url_resolver(self):
url_resolver = VideoURLResolver(parent=self)
url_resolver.error.connect(self.network_error)
url_resolver.url_resolved.connect(self.set_video_url)
url_resolver.update_status.connect(self.update_status)

return url_resolver

Expand Down Expand Up @@ -266,12 +268,11 @@ def ui_setup(self):
self.layout_main.setContentsMargins(0, 0, 0, 0)
self.layout_main.setStackingMode(QStackedLayout.StackAll)

self.status_label = StatusLabel(parent=self)
self.status_label.setMouseTracking(True)
self.status_label.setWindowFlags(Qt.WindowTransparentForInput)
self.status_label.setAttribute(Qt.WA_TransparentForMouseEvents)
self.video_status = VideoStatus(parent=self, status_text="Initializing")
self.video_status.setMouseTracking(True)
self.video_status.setWindowFlags(Qt.WindowTransparentForInput)

self.layout_main.addWidget(self.status_label)
self.layout_main.addWidget(self.video_status)
self.layout_main.addWidget(self.video_driver)
self.layout_main.addWidget(self.overlay)

Expand Down Expand Up @@ -310,10 +311,14 @@ def set_status(self, status):
self.overlay.hide()
self.video_driver.hide()

self.status_label.icon = status
self.status_label.show()
self.video_status.icon = status
self.video_status.show()
self.repaint()

def update_status(self, info_text, percent=0):
self.video_status.status_text = info_text
self.video_status.percent = percent

def reload(self):
self.is_live = False
self.is_error = False
Expand Down Expand Up @@ -581,7 +586,7 @@ def load_video_finish(self):

self.video_driver.set_playback_rate(self.video_params.rate)

self.status_label.hide()
self.video_status.hide()
self.show_overlay()

def set_aspect(self, aspect):
Expand Down
9 changes: 7 additions & 2 deletions gridplayer/widgets/video_frame_vlc_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from gridplayer.utils.qt import QABC, QT_ASPECT_MAP, qt_connect
from gridplayer.vlc_player.static import MediaInput, MediaTrack
from gridplayer.vlc_player.video_driver_base import VLCVideoDriver
from gridplayer.widgets.video_block_status import StatusLabel
from gridplayer.widgets.video_status import VideoStatus


class PauseSnapshot(QLabel):
Expand Down Expand Up @@ -54,6 +54,7 @@ class VideoFrameVLC(QWidget, metaclass=QABC):

error = pyqtSignal()
crash = pyqtSignal(str)
update_status = pyqtSignal(str, int)

is_opengl: Optional[bool] = None

Expand All @@ -72,7 +73,7 @@ def __init__(self, **kwargs):

self.ui_setup()

self.audio_only_placeholder = StatusLabel(parent=self, icon="audio-only")
self.audio_only_placeholder = VideoStatus(parent=self, icon="audio-only")
self.pause_snapshot = PauseSnapshot(parent=self)

self.ui_helper_widgets()
Expand Down Expand Up @@ -123,6 +124,7 @@ def driver_connect(self) -> None:
(self.video_driver.snapshot_taken, self.snapshot_taken),
(self.video_driver.error, self.error_emit),
(self.video_driver.crash, self.crash_emit),
(self.video_driver.update_status, self.update_status_emit),
)

def ui_setup(self) -> None:
Expand Down Expand Up @@ -155,6 +157,9 @@ def error_emit(self) -> None:

self.error.emit()

def update_status_emit(self, status: str, percent) -> None:
self.update_status.emit(status, percent)

def cleanup(self) -> Optional[bool]:
if self._is_cleanup_requested:
return True
Expand Down
5 changes: 5 additions & 0 deletions gridplayer/widgets/video_frame_vlc_hw_sp.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class PlayerProcessSingleVLCHWSP(QThread, VlcPlayerBase, metaclass=QABC):
end_reached = pyqtSignal()
time_changed = pyqtSignal(int)
error_signal = pyqtSignal()
update_status_signal = pyqtSignal(str, int)
snapshot_taken = pyqtSignal(str)

load_video_done = pyqtSignal(MediaTrack)
Expand Down Expand Up @@ -95,6 +96,9 @@ def cleanup(self):

self._cleanup_event.set()

def notify_update_status(self, status, percent=0):
self.update_status_signal.emit(status, percent)

def notify_error(self):
self.error_signal.emit()

Expand Down Expand Up @@ -156,6 +160,7 @@ def __init__(self, win_id, **kwargs):
(self.player.end_reached, self.end_reached_emit),
(self.player.time_changed, self.time_changed),
(self.player.error_signal, self.error),
(self.player.update_status_signal, self.update_status),
(self.cmd_load_video, self.player.load_video),
(self.cmd_snapshot, self.player.snapshot),
(self.cmd_play, self.player.play),
Expand Down
Loading

0 comments on commit 4c14188

Please sign in to comment.