doublesymmetry / react-native-track-player

A fully fledged audio module created for music apps. Provides audio playback, external media controls, background mode and more!
https://rntp.dev/
Apache License 2.0
3.23k stars 997 forks source link

iOS support for 'playback-metadata-received' #933

Closed puckey closed 3 years ago

puckey commented 4 years ago

It looks like this SwiftAudio pull-request is the first step to adding iOS support for 'playback-metadata-received': https://github.com/jorgenhenrichsen/SwiftAudio/pull/93

I have a hacked together version running locally and will see if I can flesh it out to match the functionality of the Android side.

Guichaguri commented 4 years ago

Nice! That would work for ID3 and Quicktime Metadata, right?

I wonder how difficult would it be to implement ICY metadata too? There are a lot of users that want to build an app for their online radio. It's a pretty simple protocol but requires a custom HTTP stream decoder.

puckey commented 4 years ago

Yes, it seems to emit ICY metadata also - which is what I am looking to use it for.

Guichaguri commented 4 years ago

That's perfect! I've just pushed to v2 Vorbis Comments and QuickTime Metadata support to Android https://github.com/react-native-kit/react-native-track-player/commit/4139370f83913b21e459ffd16611dc9ca9fbc4e4

puckey commented 4 years ago

@Guichaguri are you testing this with any specific files?

Also, I noticed icy metadata is always returned as title with artist name and track name. The separator between can be whatever the station wants (I have seen asterisks used as separators) and also the order can be either artist/track or track/artist.. sometimes one or both are all-caps. It is a mess and I am not sure we should be attempting to clean it up by splitting the string on -

puckey commented 4 years ago

I made a pr for iOS support here: https://github.com/react-native-kit/react-native-track-player/pull/937

This emits the playback-metadata-received event when a track with metadata is loaded and also emits them over time when listening to a stream.

I have yet to align things perfectly with the Android version. I was thinking it might help to define a set of source files which we can use to compare Android and iOS.

Meat and potatoes of iOS functionality is here: https://github.com/puckey/react-native-track-player/blob/bb330b675a7d9d692eb8c5fa2ec0984f19e0736e/ios/RNTrackPlayer/RNTrackPlayerAudioPlayer.swift#L83

Guichaguri commented 4 years ago

@Guichaguri are you testing this with any specific files?

Also, I noticed icy metadata is always returned as title with artist name and track name. The separator between can be whatever the station wants (I have seen asterisks used as separators) and also the order can be either artist/track or track/artist.. sometimes one or both are all-caps. It is a mess and I am not sure we should be attempting to clean it up by splitting the string on -

Yeah, at first I thought splitting them would be a good idea, but we definitely should return just the full station title. As this is a breaking change we should do that in v2

Here are some implementation details on Android:

ICY

ICY Headers

ID3 Another weird implementation, it actually tries to find the properties from a number of different tags, ideally it should only fetch one, but there is either no clear specification on which ones should be preferred or they are there for compatibility with older versions (e.g. ID3v1)

Vorbis Comments

QuickTime Metadata

puckey commented 4 years ago

Here is an overview of the AVMetadataIdentifier types on ios: https://github.com/xybp888/iOS-SDKs/blob/master/iPhoneOS13.0.sdk/System/Library/Frameworks/AVFoundation.framework/Headers/AVMetadataIdentifiers.h

We can use the common types for most of what we are looking for:

ICY

ICY Headers

Even though these identifiers exist, I have not seen a stream return ICY Headers metadata values yet on iOS..

ID3

QuickTime Metadata

puckey commented 4 years ago

I finished off my PR incorporating the implementations details as described in the comment above: https://github.com/react-native-kit/react-native-track-player/pull/937

puckey commented 3 years ago

Coming back to this, it seems iOS 12 has started returning icy metadata as AVMetadataIdentifier.icyMetadataStreamTitle, where it was using AVMetadataIdentifier.commonIdentifierTitle before

BryonConley commented 3 years ago

@puckey , @guichaguri thank you for implementing the IOS side of the playback-metadata-received event handler. I will simplify my code and not use the platform detect if / then on andling and do straight up playnack-metadata-received handling. Also, on this, as we have several shoutcast stations (transmits ID3 and Icy headers) I noticed the reat-native-track-player does not seem to pick up two items: duration and position reset when songs change on the shoutcast station. I have tried third party web radio players as HTML widgets on our website which has no problems finding duration and resets position af each new song playing. Why isn't react-nativve-track-player working like this?

Another note, the industry standards for separator is " - " (one space, a dash, and one space). Those using '*" (askerisks are bending the standards... Even in Android, I have to collect the album, title and artist values from the RN Track-player and do some more JS processing to get the album / titles / artist right. Not sure if this is our Shoutcast provider or the RN Track-Player algorithm miss.

But thanks for getting the iOS playback-metadata-received in and will be testing it fairly soon.

dodgex commented 3 years ago

Coming back to this, it seems iOS 12 has started returning icy metadata as AVMetadataIdentifier.icyMetadataStreamTitle, where it was using AVMetadataIdentifier.commonIdentifierTitle before

I just updated my app to use your changes and found the same. I decided to change the source = icy-headers to just icy and added the title split to the icy if and now it is working exactly as expected (and as in android) :)

puckey commented 3 years ago

@dodgex could you share your changes in more detail? I would be happy to wrap this pr up and merge it

dodgex commented 3 years ago

https://github.com/dodgex/react-native-track-player/commit/83b1e8659440224d478665e606f990175f96e9a4

dodgex commented 3 years ago

@puckey any news on this? :D

BryonConley commented 3 years ago

Hi, funny you mentioned the icy header and split on title. I wound up doing just this, but left the source code alone and just did it within player.js (see the react-native-track-player-example). Also, I noticed iOS 12 did solve the Icy issues so I removed the platform conditional within the metadata-received method also now in player.js. As far as issue #686 recommended code for the playerlistscreem on the togglePlayer function. I moved the metadata relevent portion to player.js added the use even (playback-metadata-received0 and the out of sync issue went away. I stll cannot solve the position and duration set and reset when a new song track is started from my shoutcast radiostations. Seems the use event for playback-metadata-receieed does not manage the position and duration and indicated in the library code I reviewed. Any idea what I should be using to trigger position and duration if it isn't the playback-metadata-received event isn't the correct method? Thanks for keeping me in the loop on this issue.

dodgex commented 3 years ago

@BryonConley The metadata provided by shoutcast and icecast servers only contain StreamTitle and StreamUrl fields. so there is no information about position and duration.

actually those fields don't even make much sends on those services as those are mostly used in some kind of live broadcasting services where the dj has full controll over where in the song the playback starts and how long the track is actually played before the next on is mixed in (at least this is true for all usecases where i have seen a shout-/icecast server in use until now).

puckey commented 3 years ago

@puckey any news on this? :D

My work goes from frontend to backend to native in waves. Will be shifting back to this in a few weeks!

puckey commented 3 years ago

@dodgex Thanks for putting up your branch. Looking at this again, it seems AVMetadataIdentifier.commonIdentifierTitle still works. It actually looks like the latest iOS is erroneously seeing the track title as .icyMetadataStreamTitle, while this is ought to be reserved for the title of the stream. So it seems we end up with a mix between the title of icy and the url of icy-headers.. Ugh! For this reason I removed the title = getMetadataItem(forIdentifier: .icyMetadataStreamTitle) part to avoid possible future breakages. See https://github.com/puckey/react-native-track-player/commit/0532000bb793f020a7676cc1ce2038613f98c78d

I think this is ready for merging. Let me know what you think.

dodgex commented 3 years ago

I tested your changes on a iOS 14.1 simulator and it works. 👍

but I do not agree with this statement:

It actually looks like the latest iOS is erroneously seeing the track title as .icyMetadataStreamTitle, while this is ought to be reserved for the title of the stream

Icecast meta (icy on-stream) only has two fields StreamTitle (represented in .icyMetadataStreamTitle) and StreamUrl (represented in .icyMetadataStreamURL. The Meta in the Header (icy-headers) sent on connect there might be more data but for this use case the on-stream meta is what is interesting. And it seems that the iOS component here does not even process the icy-headers.

The StreamTitle attribute is built depending on the data the server knows, when only a title is configured it is just that. but if there is an artist+title configured it becomes ' %s - %s'. official icecast: https://gitlab.xiph.org/xiph/icecast-server/-/blob/master/src/format_mp3.c#L284 icecast-kh fork: https://github.com/karlheyes/icecast-kh/blob/master/src/format_mp3.c#L495

so I'd say both is valid. :)

BryonConley commented 3 years ago

@puckey and others, just and FYI, we have radio servers that we can program the streamTitle more/or less fluidly on our Centova Cast. The stream are then fed to Shoutcast and there they form their own. When we point our apps to the CentovaCast, we get to make the stream title like this: %album% - %artist% - %title% . even still, we can program the album field to includ duration through string text tricks along with our App control tokens. For instance we can do MyBestAlbum:%duration% or even things like Promo:%MyBestAlbum: and make this the new %album% value as part of the custpm stream title. I'd like to try out the ios playback-metadata-received and what you have. Please let me know where I can get the swift source on the code. Thanks all for taking the time to review my request.

thecluff commented 3 years ago

@BryonConley Yo! I'm currently developing an app for my station, and we're also using Centova Cast and shoutcast. For our mobile app, I'm using react-native-track-player. I'm having weird intermittent problems with the native controls and meta data though.. Any chance you could share a repo or give me a little insight to your implementation so far? 🙏

dcvz commented 3 years ago

closing this as this has been added in v2. See comment here: https://github.com/DoubleSymmetry/react-native-track-player/pull/937#issuecomment-850900859