ironsmile / euterpe

Self-hosted music streaming server 🎶 with RESTful API and Web interface. Think of it as your very own Spotify! ☁️🎧
https://listen-to-euterpe.eu
GNU General Public License v3.0
539 stars 43 forks source link

Apple TV app #46

Open gedw99 opened 2 years ago

gedw99 commented 2 years ago

I think we could easily build an Apple TV app for this.

There are 2 options that might work. Tvml is apples own markup for Apple TV that may also support music . We could serve that from the server or another project t that imports the pkg of the server.

https://developer.apple.com/documentation/tvmljs/playing_media_in_a_client-server_app

The other way is with golang . You can build advanced golang gui that can play music using gioui. It compiles to iOS and hence apple tv. I use gioui a fair bit.

It can also be used to output web and mobile and desktop apps.

https://github.com/gioui

ironsmile commented 2 years ago

Oh, Gio is very intriguing! I've toyed with it for a while and was evaluating it for the Euterpe Desktop client. But ultimately decided to use GTK as it was much more familiar and mode some mobile/desktop convergent stuff easier.

As for the actual question, I've had bad experience developing apps for apple in the past. Ideally I would avoid myself it if I could. What I've thought before is maybe taking advantage of already existing music clients by implementing a widely used API. Such Subsonic's. I think it would be quite easy to make Euterpe expose the Subsonic API and one would be able to connect to it using any Subsonic client already present.

gedw99 commented 2 years ago

Hey

GTK is also good. I used it also.

I tested https://developer.apple.com/documentation/tvmljs/playing_media_in_a_client-server_app on my Apple TV and it works It was just a matter of feeding the tvml from the server The apple tv then calls for the audio.

Subsonic looks really good. Its a great Idea to expose the APi using Subsonic. Now that i knwo the Apple TVML works, we can on the server call subsonic, and then write a provider to feed Apple TVML to the Apple TV if we want.

So then if anyone wants to use Apple tv with euterpe they can.

I only tried this out because i like the flexibility of tvml, and how you can then hand off the audio to your house speakers also and also Home equipment. I know its lockin, but i just happen to have one. I also think that euterpe shoudl try to be agnostic to support devices that people happen to own.

gedw99 commented 2 years ago

https://github.com/delucks/go-subsonic looks ok maybe ...

Oh https://github.com/sentriz/gonic is much better. Seems to be updated too

ironsmile commented 2 years ago

You are completely right. I don't see why not supporting both TVML and Subsonic API. But I am not exactly sure what is exactly that the server should do. Maybe that stems from the fact that I don't understand what TVML is exactly. One creates TVML screens for the client app which runs on the TV. That part I do get, I think. Or is that the server has to serve the XML files (e.g. this one for lists)? If that's so then I am open to trying my hand on implementing a simple version provided I could use some sort of a Apple TV simulator since I don't have a real one.

gedw99 commented 2 years ago

The server serves the tvml. The client loads data from the server to render it. It's like a browser is running inside the Apple TV, but it's not a browser, just a JS interpreter. Its essentially Custom Elements, in that it renders custom html elements.

The quality of the UX for the user is very high, with only tiny amounts of the JS and xml code.

Here is the code form the example.

initialpage.xml:

<document>
    <stackTemplate>
        <background>
            <audio>
                <asset src="http://localhost:9001/Server/Media/Rhythm.aif" />
            </audio>
        </background>
        <banner>
            <title>Playing Media Items</title>
        </banner>
        <collectionList>
            <shelf>
                <section>
                    <lockup onselect="playMedia('Server/Media/video1.mp4', 'video')">
                        <img src="http://localhost:9001/Server/Images/Beach_Movie_250x375_A.png" width="182" height="274"/>
                        <title>Video</title>
                    </lockup>
                    <lockup onselect="playMedia('Server/Media/Synth.aif', 'audio')">
                        <img src="http://localhost:9001/Server/Images/Car_Movie_250x375_C.png" width="182" height="274" />
                        <title>Audio</title>
                    </lockup>
                </section>
            </shelf>
        </collectionList>
    </stackTemplate>
</document>

client side js. You can see it is loading the "initialpage.xml" !

/*
See LICENSE folder for this sample’s licensing information.

Abstract:
JavaScript methods to load data from a server and play the corresponding media.
*/

var baseURL;

function loadingTemplate() {
    var template = '<document><loadingTemplate><activityIndicator><text>Loading</text></activityIndicator></loadingTemplate></document>';
    var templateParser = new DOMParser();
    var parsedTemplate = templateParser.parseFromString(template, "application/xml");
    navigationDocument.pushDocument(parsedTemplate);
}

function getDocument(extension) {
    var templateXHR = new XMLHttpRequest();
    var url = baseURL + extension;

    loadingTemplate();
    templateXHR.responseType = "document";
    templateXHR.addEventListener("load", function() {pushPage(templateXHR.responseXML);}, false);
    templateXHR.open("GET", url, true);
    templateXHR.send();
}

function pushPage(document) {
    var currentDoc = getActiveDocument();
    if (currentDoc.getElementsByTagName("loadingTemplate").item(0) == null) {
        navigationDocument.pushDocument(document);
    } else {
        navigationDocument.replaceDocument(document, currentDoc);
    }
}

function playMedia(extension, mediaType) {
    var mediaURL = baseURL + extension;
    var singleMediaItem = new MediaItem(mediaType, mediaURL);
    var mediaList = new Playlist();

    mediaList.push(singleMediaItem);
    var myPlayer = new Player();
    myPlayer.playlist = mediaList;
    myPlayer.play();
}

App.onLaunch = function(options) {
    baseURL = options.BASEURL;
    var extension = "/Server/Templates/initialpage.xml";
    getDocument(extension);
}