From 4cf9a2a58f784581e6a6726e6d7da3f704795f97 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Sat, 7 Jun 2025 22:43:22 +0200 Subject: [PATCH 1/2] Improve casting resume behavior --- app/controller.go | 15 ++++++--------- app/model.go | 32 +++++++++++++++++++++++++++++++- app/view.go | 21 ++++++++++++++++++++- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/app/controller.go b/app/controller.go index baa2f68..9ad0438 100644 --- a/app/controller.go +++ b/app/controller.go @@ -39,16 +39,10 @@ func (c *Controller) StartCasting(selected string, mediaURL string) { return } - err := c.Model.ConnectToDevice(selected) - if err != nil { - c.LogAndShowError(fmt.Errorf("error connecting to device %v", err)) - return - } go func() { - err = c.Model.StartCast(mediaURL) + err := c.Model.Cast(selected, mediaURL) if err != nil { - c.LogAndShowError(fmt.Errorf("error starting cast %v", err)) - c.DiscoverDevices() + c.LogAndShowError(fmt.Errorf("error starting cast: %v", err)) return } }() @@ -56,6 +50,10 @@ func (c *Controller) StartCasting(selected string, mediaURL string) { } +func (c *Controller) ShowReconnecting(reconnecting bool) { + c.View.PopupReconnecting(reconnecting) +} + // Note: these are app codes, they are NOT related to fcast opcodes! const ( ActionPlay = 0 @@ -78,7 +76,6 @@ func (c *Controller) PlayerAction(action int, args ...float32) { } func (c *Controller) ExitCasting() { - c.PlayerAction(ActionStop) c.DiscoverDevices() } diff --git a/app/model.go b/app/model.go index 42bc090..52d6bb3 100644 --- a/app/model.go +++ b/app/model.go @@ -7,6 +7,8 @@ import ( "io" "math" "net/http" + "strings" + "time" ) type Model struct { @@ -14,9 +16,11 @@ type Model struct { DiscoveredDevices []fcast.DiscoveredHost Connection *fcast.Connection EventManager *fcast.EventManager + DeviceName string Time float32 Length float32 Volume float32 + IsReconnecting bool } func (m *Model) DiscoverDevices() ([]string, error) { @@ -47,10 +51,29 @@ func (m *Model) ConnectToDevice(name string) error { return err } m.Connection = connection + m.DeviceName = name return nil } -func (m *Model) StartCast(mediaURL string) error { +func (m *Model) Cast(selectedDevice, mediaURL string) error { + err := m.doCast(selectedDevice, mediaURL) + if err != nil { + if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "software caused connection abort") { + m.Controller.ShowReconnecting(true) + time.Sleep(500 * time.Millisecond) + return m.Cast(selectedDevice, mediaURL) + } + } + m.Controller.ShowReconnecting(false) + return err +} + +func (m *Model) doCast(selectedDevice, mediaURL string) error { + + err := m.ConnectToDevice(selectedDevice) + if err != nil { + return fmt.Errorf("error connecting to device %v", err) + } m.EventManager = &fcast.EventManager{} @@ -81,6 +104,13 @@ func (m *Model) StartCast(mediaURL string) error { if err != nil { return err } + } else { + m.Volume = 1 + } + + err = m.Connection.SendMessage(&fcast.PingMessage{}) + if err == nil { + m.Controller.ShowReconnecting(false) } return m.Connection.ListenForMessages(m.EventManager) diff --git a/app/view.go b/app/view.go index e105359..279249e 100644 --- a/app/view.go +++ b/app/view.go @@ -18,10 +18,29 @@ type View struct { Controller *Controller Window fyne.Window CastingScreenElements CastingScreenElements + ReconnectingDialog *dialog.CustomDialog } func (v *View) PopupError(err error) { - dialog.ShowError(err, v.Window) + fyne.Do(func() { + dialog.ShowError(err, v.Window) + }) +} + +func (v *View) PopupReconnecting(show bool) { + if v.ReconnectingDialog != nil && !show { + fyne.DoAndWait(func() { + v.ReconnectingDialog.Dismiss() + v.ReconnectingDialog = nil + }) + return + } + if v.ReconnectingDialog == nil && show { + v.ReconnectingDialog = dialog.NewCustomWithoutButtons("Reconnecting to stream...", widget.NewProgressBarInfinite(), v.Window) + fyne.Do(func() { + v.ReconnectingDialog.Show() + }) + } } func NewView(controller *Controller) *View { From 31fe6ba022ce0981c33a5a8770748e8ca4ab9dad Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Mon, 16 Jun 2025 10:38:51 +0200 Subject: [PATCH 2/2] Clarify project scope --- README.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 49be5e1..8d7d1b0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,36 @@ # FCaster -FCaster is a native cross-platform application to cast media to a [FCast](https://fcast.org) Receiver, built with [Fyne](https://fyne.io). -The protocol logic is all packaged into the [FCast Library](https://pkg.go.dev/git.massive.box/massivebox/fcaster/fcast), which you can use in your own projects to build a FCast Sender! +FCaster is a Go [library](https://pkg.go.dev/git.massive.box/massivebox/fcaster/fcast) implementing a [FCast](https://fcast.org) Sender, which allows to send media to a Receiver. It also has a simple cross-platform application, built with [Fyne](https://fyne.io), to showcase its features. -Currently, the app only allows you to stream from URLs, but I will add a way to stream local files and Jellyfin media directly. (You can already stream Jellyfin media by copying the stream URL from Jellyfin and pasting it in the app - [detailed instructions](https://s.massive.box/fcaster-jellyfin)). +**Notice**: FCaster is NOT associated to, or endorsed by, FUTO. It's an unofficial implementation of the FCast Protocol. -If you're on Android, I recommend you [disable battery optimizations](https://support.google.com/pixelphone/thread/299966895/turn-off-battery-optimization-for-an-app?hl=en) for FCaster, otherwise the app will disconnect when your device locks. +## Library -## Screenshots +The [FCast library](https://pkg.go.dev/git.massive.box/massivebox/fcaster/fcast) allows you to stream media to a FCast Receiver from your own software! + +| Feature | Supported | +|-------------------|-----------| +| TCP | ✅ | +| WebSockets | ❌ | +| mDNS Discovery | ✅ | +| Outgoing Commands | ✅ | +| Incoming Commands | ✅ | +| QR Code | ❌ | + +Since the library is in active development, its stability is not guaranteed. + +## App + +Due to the limitations of Fyne on Android (not implementing [foreground services](https://developer.android.com/guide/components/foreground-services)), the FCaster App will not support features such as local media casting in the near future. We are working on an alternative implementation. + +Currently, the app only allows you to stream from URLs. (You can already stream Jellyfin media by copying the stream URL from Jellyfin and pasting it in the app - [detailed instructions](https://s.massive.box/fcaster-jellyfin)). + +### Screenshots | ![Main view](assets/mainView.png) | ![Cast view](assets/castView.png) | -| --------------------------------- |-----------------------------------| +| - | - | -## Download +### Download Head over to the [Releases](https://git.massive.box/massivebox/fcaster/releases) page to find builds for your device. You can use [Obtanium](https://obtainium.imranr.dev/) to get automatic updates on Android.