ubuntu-flutter-community / musicpod

Music, radio, television and podcast player for Ubuntu, Windows, MacOs and Android

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Chromecast support

Feichtmeier opened this issue · comments

Okay so, I can't really contribute to any of this, my Flutter know-how is pretty basic, but I just wanted to express my excitement about this feature.
Think its a really good idea, I'm sure many people have at least one chromecast-capable device, like Smart-TVs, GoogleSpeakers and more. It would be nice to be able to click a button and listen your podcast, song, or FM station on one of those. It also provides some good kind of UX that has been missing from the linux community, you know the "google is evil", or the "we don't do big tech" vibe.
Can't wait to use it!

Oh I've done something to work with Fuchsia (it`s early working) I'll make some tests if work I can PR it or push on someone's package supports to linux

@allansrc wow that would be amazing!
Thanks for investing time

@guyluz11 thank you I tried your branch and it works as I can list the devices and can connec tto them and can send messages to them. Maybe I misunderstand something with how chromecast works, but can I send for example a podcast or a radio station to it?

I just changed the very narrow bottom player (when musicpod is very narrow) to this, and it works as described above:

import 'package:cast/device.dart';
import 'package:cast/discovery_service.dart';
import 'package:cast/session.dart';
import 'package:cast/session_manager.dart';
import 'package:flutter/material.dart';

import '../../player.dart';
import 'bottom_player_image.dart';
import 'bottom_player_title_artist.dart';
import 'play_button.dart';

class VeryNarrowBottomPlayer extends StatelessWidget {
  const VeryNarrowBottomPlayer({
    super.key,
    required this.setFullScreen,
    required this.bottomPlayerImage,
    required this.titleAndArtist,
    required this.active,
    required this.isOnline,
    required this.track,
  });

  final void Function(bool? p1) setFullScreen;
  final BottomPlayerImage bottomPlayerImage;
  final BottomPlayerTitleArtist titleAndArtist;
  final bool active;
  final bool isOnline;
  final PlayerTrack track;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => setFullScreen(true),
      child: SizedBox(
        height: kBottomPlayerHeight,
        child: Column(
          children: [
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Padding(
                    padding: const EdgeInsets.only(left: 8),
                    child: bottomPlayerImage,
                  ),
                  const SizedBox(
                    width: 20,
                  ),
                  Expanded(
                    child: titleAndArtist,
                  ),
                  Padding(
                    padding: const EdgeInsets.only(right: 20),
                    child: PlayButton(active: active),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(right: 20),
                    child: IconButton(
                      onPressed: () => showDialog(
                        context: context,
                        builder: (context) => const CastTestDialog(),
                      ),
                      icon: const Icon(Icons.cast),
                    ),
                  ),
                ],
              ),
            ),
            track,
          ],
        ),
      ),
    );
  }
}

class CastTestDialogContent extends StatelessWidget {
  const CastTestDialogContent({super.key});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<CastDevice>>(
      future: CastDiscoveryService().search(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Center(
            child: Text(
              'Error: ${snapshot.error.toString()}',
            ),
          );
        } else if (!snapshot.hasData) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }

        if (snapshot.data!.isEmpty) {
          return const Column(
            children: [
              Center(
                child: Text(
                  'No Chromecast founded',
                ),
              ),
            ],
          );
        }

        return Column(
          children: snapshot.data!.map((device) {
            return ListTile(
              title: Text(device.name),
              onTap: () => _connect(context, device),
            );
          }).toList(),
        );
      },
    );
  }

  Future<void> _connect(BuildContext context, CastDevice object) async {
    final session = await CastSessionManager().startSession(object);

    session.stateStream.listen((state) {
      if (state == CastSessionState.connected) {
        _sendMessage(session);
      }
    });

    session.messageStream.listen((message) {
      print('receive message: $message');
    });

    session.sendMessage(CastSession.kNamespaceReceiver, {
      'type': 'LAUNCH',
      'appId': 'YouTube', // set the appId of your app here
    });
  }

  void _sendMessage(CastSession session) {
    session.sendMessage('urn:x-cast:namespace-of-the-app', {
      'type': 'sample',
    });
  }
}

class CastTestDialog extends StatelessWidget {
  const CastTestDialog({super.key});

  @override
  Widget build(BuildContext context) {
    return const AlertDialog(
      content: CastTestDialogContent(),
      contentPadding: EdgeInsets.only(top: 20, bottom: 20),
    );
  }
}

I didn't manage to open YouTube links.
To do that I think there are 3 options

  1. Opening the link in the Chromecast browser like a browser web page (will look awkward and bad performance).
  2. Opening it in a video player, I think this is what node-red-contrib-castv2 did. This is also not the best experience because you are not inside of the YouTube app and the controls behave wired.
  3. Support a new dedicated Chromecast YouTube API, which I think will require the user to authenticate but will be like casting a video from the YouTube app. I think the following links show an implementation of it https://github.com/castjs/castjs and https://bugs.xdavidhu.me/google/2021/04/05/i-built-a-tv-that-plays-all-of-your-private-youtube-videos/

Maybe I will work on it in the future.
I will post about my progress in the following link (in the cbj repo issue)
CyBear-Jinni/cbj_integrations_controller#15

I didn't manage to open YouTube links.
To do that I think there are 3 options

  1. Opening the link in the Chromecast browser like a browser web page (will look awkward and bad performance).
  2. Opening it in a video player, I think this is what node-red-contrib-castv2 did. This is also not the best experience because you are not inside of the YouTube app and the controls behave wired.
  3. Support a new dedicated Chromecast YouTube API, which I think will require the user to authenticate but will be like casting a video from the YouTube app. I think the following links show an implementation of it https://github.com/castjs/castjs and https://bugs.xdavidhu.me/google/2021/04/05/i-built-a-tv-that-plays-all-of-your-private-youtube-videos/

Maybe I will work on it in the future.
I will post about my progress in the following link (in the cbj repo issue)
CyBear-Jinni/cbj_integrations_controller#15

Hi 👋😊

I don't want to open it inside YouTube

Only stream the audio and or video (video podcasts) to a Chromecast device

Didn't check streaming but it should be a small change to already existing request

Didn't check streaming but it should be a small change to already existing request

Alright!
I would really appreciate a pull request if you have a plan :)

commented

I have a 3rd gen chromecast, it pretty much only supports H264 video and a very narrow range of audio codecs. You would need to transcode the media files for casting them.
This codec issue is why even VLC fails to cast stuff to my chromecast.