#!/usr/bin/env python3 import threading import time from typing import NewType import pychromecast import wx import wx.lib.scrolledpanel as scrolled import wx.media from ChannelProvider import SVT, ChannelProvider, YouTube ChannelProvider = NewType('ChannelProvider', ChannelProvider) class Cast(wx.Frame): def __init__(self, *args, **kw): """__init__. :param args: :param kw: """ super().__init__(*args, **kw) self.m_selected_chromecast = None self.SetSizeHints(480, 480, maxW=480) self.m_index = 0 self.m_chromecast_thr = threading.Thread(target=self.get_chromecasts, args=(), kwargs={}) self.m_chromecast_thr.start() self.m_sizer: wx.Sizer = wx.BoxSizer(wx.VERTICAL) self.m_panel: wx.lib.scrolledpanel.ScrolledPanel = scrolled.ScrolledPanel( self, -1, style=wx.VSCROLL) self.m_control = None self.m_panel.SetupScrolling() self.m_panel.SetSizer(self.m_sizer) self.m_providers: list[ChannelProvider] = [ YouTube.YouTube('UCu6mSoMNzHQiBIOCkHUa2Aw'), SVT.SVT() ] self.m_selected_channel = None self.show_splash(None) def get_chromecasts(self) -> None: """ [TODO:description] :rtype None: [TODO:description] """ self.m_chromecasts, self.m_browser = pychromecast.get_chromecasts() def show_splash(self, _) -> None: self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.BoxSizer(wx.VERTICAL) channel_index = 0 for provider in self.m_providers: bitmap = provider.get_logo_as_bitmap() btn = wx.BitmapButton(self.m_panel, id=channel_index, bitmap=bitmap) btn.Bind(wx.EVT_BUTTON, lambda event, index=channel_index: self.show_list( event, index)) self.m_sizer.Add(btn) channel_index += 1 self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def show_list(self, _, index=0) -> None: """ Shows a list of videos :param _ [TODO:type]: [TODO:description] :rtype None: [TODO:description] """ self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.BoxSizer(wx.VERTICAL) channel = self.m_providers[index] if channel.wait(): with wx.BusyInfo("Please wait, working..."): index = 0 while channel.wait(): time.sleep(1) wx.GetApp().Yield() backbtn = wx.Button(self.m_panel, -1, label="Go back", size=(480, 40)) backbtn.Bind(wx.EVT_BUTTON, lambda event: self.show_splash(event)) self.m_sizer.Add(backbtn, wx.ALIGN_CENTER_VERTICAL) self.m_sizer.AddSpacer(10) for item in channel.get_items(): title = wx.StaticText(self.m_panel, -1) title.SetLabelMarkup("{}".format( item['title'])) description = wx.StaticText(self.m_panel, -1, item['description']) description.Wrap(478) bitmap = item['thumbnail'] btn = wx.BitmapButton(self.m_panel, id=self.m_index, bitmap=bitmap) btn.Bind(wx.EVT_BUTTON, lambda event, link=item['link'], provider_index=index: self.show_player(event, link, provider_index)) self.m_sizer.Add(title) self.m_sizer.Add(btn) self.m_sizer.Add(description) self.m_sizer.AddSpacer(10) self.m_index = self.m_index + 1 self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def show_player(self, _, uri, provider_index: int): """ Show the video player :param _ event: unused :param uri str: the link to the video stream """ self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.GridBagSizer() self.m_control = wx.media.MediaCtrl( self.m_panel, size=(480, 480), style=wx.SIMPLE_BORDER, szBackend=wx.media.MEDIABACKEND_GSTREAMER) play_button = wx.Button(self.m_panel, -1, "Play") pause_button = wx.Button(self.m_panel, -1, "Pause") back_button = wx.Button(self.m_panel, -1, "Back") back_button.Bind( wx.EVT_BUTTON, lambda event, index=provider_index: self.show_list(event, index)) self.m_sizer.Add(self.m_control, (0, 0)) self.m_sizer.SetItemSpan(0, (0, 6)) self.m_sizer.Add(play_button, (1, 1)) self.m_sizer.Add(pause_button, (1, 2)) self.m_sizer.Add(back_button, (1, 3)) if not self.m_chromecast_thr.is_alive( ) and not self.m_selected_chromecast: chromecast_button = wx.Button(self.m_panel, -1, "Cast") chromecast_button.Bind( wx.EVT_BUTTON, lambda event, muri=uri: self.select_chromecast(event, muri)) self.m_sizer.Add(chromecast_button, (2, 2)) if self.m_selected_chromecast: self.Bind(wx.media.EVT_MEDIA_LOADED, lambda event, muri=uri: self.cast(event, muri)) play_button.Bind(wx.EVT_BUTTON, self.play_cast) pause_button.Bind(wx.EVT_BUTTON, self.pause_cast) else: self.Bind(wx.media.EVT_MEDIA_LOADED, self.play) play_button.Bind(wx.EVT_BUTTON, self.play) pause_button.Bind(wx.EVT_BUTTON, self.pause) self.Bind(wx.media.EVT_MEDIA_FINISHED, self.show_list) self.load_uri(uri) self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() self.m_panel.ScrollChildIntoView(self.m_control) def select_chromecast(self, _, uri): self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.BoxSizer(wx.VERTICAL) for cast in self.m_chromecasts: friendly_name = cast.cast_info.friendly_name btn = wx.Button(self.m_panel, id=-1, label=friendly_name, size=(480, 40)) btn.Bind(wx.EVT_BUTTON, lambda event, chromecast=cast, muri=uri: self. set_chromecast(event, chromecast, muri)) self.m_sizer.Add(btn, wx.ALIGN_CENTER_VERTICAL) self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def set_chromecast(self, event, chromecast, uri): self.m_selected_chromecast = chromecast self.show_player(event, uri) def cast(self, event, uri): mimetype = 'video/mp4' cast = self.m_selected_chromecast cast.wait() cast.media_controller.play_media(uri, mimetype) # Wait for player_state PLAYING player_state = None has_played = False while True: if player_state != cast.media_controller.status.player_state: player_state = cast.media_controller.status.player_state print("Player state:", player_state) if player_state == "PLAYING": has_played = True break if cast.socket_client.is_connected and has_played and player_state != "PLAYING": has_played = False cast.media_controller.play_media(uri, mimetype) time.sleep(0.1) self.m_browser.stop_discovery() def pause_cast(self, event): cast = self.m_selected_chromecast cast.media_controller.pause() def play_cast(self, event): cast = self.m_selected_chromecast cast.media_controller.play() def load_uri(self, uri): self.m_control.LoadURI(uri) def play(self, _): self.m_control.Play() def pause(self, _): self.m_control.Pause() def quit(self, _): self.Destroy() if __name__ == '__main__': # When this module is run (not imported) then create the app, the # frame, show it, and start the event loop. app: wx.App = wx.App() frm: Cast = Cast(None, title='Cast') frm.Show() app.MainLoop()