tobiasg
tobiasg

Reputation: 1073

Media Session API on iOS 16, does it require name/URL of website?

I'm playing around with a PWA web app created in Vue and I've added this as a Home Screen bookmark to my iPhone running iOS 16.

When I play an audio file in the app, I create a Media Session instance that looks like this:

if ("mediaSession" in navigator) {
                navigator.mediaSession.metadata = new MediaMetadata({
                    title: this.currentSong.title,
                    artist: this.currentSong.artist,
                    album: this.currentSong.album,
                    artwork: [
                        {
                            src: "https://dummyimage.com/96x96",
                            sizes: "96x96",
                            type: "image/png",
                        },
                        ...
                    ],
                });
                navigator.mediaSession.setActionHandler("nexttrack", () => {
                    this.nextSong();
                });
                navigator.mediaSession.setActionHandler("previoustrack", () => {
                    this.prevSong();
                });
                navigator.mediaSession.setActionHandler("seekto", (details) => {
                    this.emitter.emit("seek", details.seekTime);
                });
                navigator.mediaSession.setActionHandler("play", () => {
                    this.togglePlay();
                });
                navigator.mediaSession.setActionHandler("pause", () => {
                    this.togglePlay();
                });
            }

None of the methods used in this is relevant for my question, it has more to do with how iOS handles the information I provide.

Whenever I create a new MediaMetadata and provides title, artist etc. the source or name of the app disappears from the media player widget on the Lock Screen. Clicking the cover art of the player, I'm redirected to what seems to be the first best PWA on my device that iOS can find, almost as if it just takes a list of all PWA apps installed and just picks the first. It would of course be more expected that iOS directs me to the PWA app that the audio is coming from.

If I don't provide anything through the Media Session API and comment out the code above, the media player seems to at least understand where the audio is coming from, since the title of the website is written out and I can click the cover art and it redirects me to the correct PWA, but obviously without any metadata since I haven't provided it.

I also noticed that creating one action handler, for example navigator.mediaSession.setActionHandler("nexttrack", () => {...}), would reset all actions in the player, so the play button would for example be deactivated by providing just one action handler. I was thinking if there is something similar with providing metadata, that all default properties gets nulled or similar?

I didn't see this behavior on iOS 15 so I'm just curious if this is a bug with iOS 16 or if I have to provide any extra properties with the MediaMetadata instance?

Upvotes: 2

Views: 792

Answers (1)

jbwharris
jbwharris

Reputation: 720

I know this is an old question, but I feel like I can offer some insight. I've been building a PWA that works with CarPlay and iOS 17 and 18. The behaviour I've noted is that you can send the song, artist, album and album art to MediaMetadata and you may see a hiccup of it defaulting to the HTML page title briefly. I've actually used this to my benefit, as my app is for playing radio streams and it needs a moment to switch stations and load the metadata from the APIs. So I pass a loading message to the page title, then assign the song, artist etc to MediaMetadata once I've pulled it. In my experience with iOS, I haven't been able to get more than the play/pause/next/previous button to display concurrently. It's either the skip 10 seconds forward/back, or next/previous, but not both.

if ("mediaSession" in navigator) {
        navigator.mediaSession.metadata = new MediaMetadata({
            title: song || 'No song available',
            artist: artist || '',
            album: album || '',
            duration: Infinity,
            startTime: 0,
            artwork: [
                {
                    src: artworkUrl,
                },
            ],
        });

        navigator.mediaSession.setActionHandler('nexttrack', () => {
            console.log('User clicked "Next Track" icon.');
            this.radioPlayer.skipForward();
        });

        navigator.mediaSession.setActionHandler('previoustrack', () => {
            console.log('User clicked "Previous Track" icon.');
            this.radioPlayer.skipBackward();
        });

        navigator.mediaSession.setActionHandler('play', async () => {
            console.log('User clicked "Play" icon.');
            this.radioPlayer.togglePlay();
        });

        navigator.mediaSession.setActionHandler('pause', () => {
            console.log('User clicked "Pause" icon.');
            this.radioPlayer.togglePlay();
        });
    }

As for the picking the first PWA issue, I think I encountered something to that effect with iOS. When I'd skip between stations, it would at times just default to Apple Music. The PWA would actually switch stations in the app, but by then it would have lost playback control to Apple Music. The way I navigated this was having 2 audio streams, the one playing, then the one that was requested to load. This would create a scenario where the PWA never actually gives up playback control, since the 2 Audio objects would overlap and just hand off from one to the next.

        // Create a new Audio object for the selected station
        const newAudio = new Audio(stations[newStationName].streamUrl);
        newAudio.onloadedmetadata = () => {
            // Audio loaded, now delete previous audio and update reference
            if (this.audio) {
                this.audio = null; // Delete previous audio
            }
            this.audio = newAudio; // Update reference to new audio
            this.play(); // Start playback

            fetchDataAndRefreshPage();
            this.streamingInterval = setInterval(fetchDataAndRefreshPage, 25000); // Set new interval
        };

        newAudio.onerror = (error) => {
            // Handle audio loading error
            console.error('Error loading audio:', error);
            // You can add code here to display a message to the user or take other appropriate actions.
        };

        this.audio.pause(); // Pause the previous audio

        // Start loading audio (metadata will trigger the rest)
        newAudio.load();

Upvotes: 0

Related Questions