#!/usr/bin/env python3 import threading import time import pychromecast import wx import wx.lib.scrolledpanel as scrolled import wx.media from Channel import SVT, YouTube from ChannelProvider import ChannelProvider from Utils import make_sized_button WIDTH = 720 HEIGHT = 1440 BTN_HEIGHT = 40 SPACER_HEIGHT = 10 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(WIDTH, HEIGHT, maxW=WIDTH) 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] = [ ChannelProvider( "SVT", channels=[ SVT.SVT("feed"), SVT.SVT("ch-svt1"), SVT.SVT("ch-svt2"), SVT.SVT("ch-svt24"), ], ), ChannelProvider( "YouTube", channels=[ YouTube.YouTube("UCtESv1e7ntJaLJYKIO1FoYw"), YouTube.YouTube("UC9-y-6csu5WGm29I7JiwpnA"), YouTube.YouTube("UCoxcjq-8xIDTYp3uz647V5A"), YouTube.YouTube("UCu6mSoMNzHQiBIOCkHUa2Aw"), ], ), ] self.m_selected_channel = None self.m_selected_provider_index = None self.show_provider_list(None) def get_chromecasts(self) -> None: """ [TODO:description] :rtype None: [TODO:description] """ self.m_chromecasts, self.m_browser = pychromecast.get_chromecasts() def show_provider_list(self, _) -> None: self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.BoxSizer(wx.VERTICAL) provider_index = 0 for provider in self.m_providers: bitmap = provider.get_logo_as_bitmap() callback = lambda event, index=provider_index: self.show_channel_list(event, index) btn_sizer: wx.BoxSizer = make_sized_button(self.m_panel,bitmap,provider.get_name(),callback) self.m_sizer.Add(btn_sizer) provider_index += 1 self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def show_channel_list(self, _, provider_index) -> None: self.m_selected_provider_index = provider_index self.m_selected_provider = self.m_providers[provider_index] self.m_sizer.Clear(delete_windows=True) self.m_sizer = wx.BoxSizer(wx.VERTICAL) backbtn = wx.Button(self.m_panel, -1, label="Go back", size=(WIDTH, BTN_HEIGHT)) backbtn.Bind(wx.EVT_BUTTON, lambda event: self.show_provider_list(event)) self.m_sizer.Add(backbtn, wx.ALIGN_CENTER_VERTICAL) self.m_sizer.AddSpacer(SPACER_HEIGHT) channel_index = 0 for channel in self.m_selected_provider.get_channels(): bitmap = channel.get_logo_as_bitmap() callback = lambda event, index=channel_index: self.show_video_list(event, index) btn_sizer: wx.BoxSizer = make_sized_button(self.m_panel,bitmap,channel.get_name(),callback) self.m_sizer.Add(btn_sizer) channel_index += 1 self.m_sizer.AddSpacer(SPACER_HEIGHT) self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def show_video_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_selected_provider.get_channel_by_index(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=(WIDTH, BTN_HEIGHT)) backbtn.Bind( wx.EVT_BUTTON, lambda event: self.show_channel_list(event, self.m_selected_provider_index), ) self.m_sizer.Add(backbtn, wx.ALIGN_CENTER_VERTICAL) self.m_sizer.AddSpacer(SPACER_HEIGHT) 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(SPACER_HEIGHT) self.m_index = self.m_index + 1 self.m_sizer.AddSpacer(SPACER_HEIGHT) 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=(WIDTH, HEIGHT), 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_video_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, index=provider_index: self.select_chromecast( event, muri, index ), ) 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_video_list) self.m_sizer.AddSpacer(SPACER_HEIGHT) 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, provider_index): 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=(WIDTH, BTN_HEIGHT)) btn.Bind( wx.EVT_BUTTON, lambda event, chromecast=cast, muri=uri, index=provider_index: self.set_chromecast( event, chromecast, muri, index ), ) self.m_sizer.Add(btn, wx.ALIGN_CENTER_VERTICAL) self.m_sizer.AddSpacer(SPACER_HEIGHT) self.m_panel.SetSizer(self.m_sizer) self.m_sizer.Fit(self) self.m_panel.Layout() def set_chromecast(self, event, chromecast, uri, provider_index): self.m_selected_chromecast = chromecast self.show_player(event, uri, provider_index) 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()