#!/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 Channel import SVT, Channel, YouTube from ChannelProvider import 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] = [ 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() btn = wx.BitmapButton(self.m_panel, id=provider_index, bitmap=bitmap) btn.Bind( wx.EVT_BUTTON, lambda event, index=provider_index: self.show_channel_list( event, index ), ) self.m_sizer.Add(btn) 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=(480, 40)) 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(10) channel_index = 0 for channel in self.m_selected_provider.get_channels(): bitmap = channel.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_video_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_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=(480, 40)) 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(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_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.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=(480, 40)) 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_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()