0

Hiya I'm trying to make a simple radio application with the following stream: http://lb.topradio.be/topradio.mp3.

Currently I'm using ExoMedia for convenience as my default audioplayer.
It has this setMetadataListener method which never gets called because in the Mp3Extractor class the peekId3Data method has this check.

if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) {
    // Not an ID3 tag.
    break;
}

However when I throw the same stream in VLC Media Player it is able to successfully fetch the metadata from the stream.
How does this work?
Can I replicate this with a custom Extractor ?

Thanks

timr
  • 6,668
  • 7
  • 47
  • 79

1 Answers1

0

There is an issue opened on the Exoplayer repository. Basically the metadata is transferred through headers, not in the stream itself.

I had a success with the audiostream-metadata-retriever although I had some track synchronization problems because of the stream caching and metadata arriving before the actual song starts playing.

Edit:

RxJava implementation:

private long icyMetaInt = 1000;

public void showStreamData() {
    Log.d(TAG, "Show stream metadata");
    streamService.getStreamData()
            .subscribeOn(Schedulers.io())
            .repeatWhen(delays -> delays.concatMap(metaIntDelay -> {
                Log.d(TAG, "icyMetaDelay = " + icyMetaInt + "ms ");
                return Observable.timer(icyMetaInt, TimeUnit.MILLISECONDS);
            }))
            .subscribe(new Observer<Response<ResponseBody>>() {
                @Override
                public void onSubscribe(Disposable disposable) {
                    Log.d(TAG, "OnSubscribe");
                }

                @Override
                public void onNext(Response<ResponseBody> response) {
                    Log.d(TAG, "onNext - " + response);
                    if (response.isSuccessful()) {
                        InputStream stream = response.body().byteStream();
                        if (stream != null) {
                            icyMetaInt = Integer.parseInt(response.headers().get("icy-metaint"));
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            try {
                                long skipped = stream.skip(icyMetaInt);
                                while (skipped != icyMetaInt) {
                                    skipped += stream.skip(icyMetaInt - skipped);
                                }

                                int symbolLength = stream.read();
                                int metaDataLength = symbolLength * 16;
                                if (metaDataLength > 0) {
                                    for (int i = 0; i < metaDataLength; ++i) {
                                        int metaDataSymbol = stream.read();
                                        if (metaDataSymbol > 0) {
                                            baos.write(metaDataSymbol);
                                        }
                                    }

                                    String result = baos.toString()
                                            .replace("StreamTitle=", "")
                                            .replaceAll("'", "")
                                            .replaceAll(";", "");
                                    baos.reset();

                                    Log.d(TAG, result);
                                    runOnUiThread(() -> textView.setText(result));

                                    Log.d(TAG, response.headers().get("ice-audio-info"));
                                    Log.d(TAG, response.headers().get("icy-description"));
                                    Log.d(TAG, response.headers().get("icy-genre"));
                                    Log.d(TAG, response.headers().get("icy-name"));
                                    Log.d(TAG, response.headers().get("icy-url"));
                                }

                            } catch (IOException e) {
                                onError(e);
                                Log.e(TAG, "Failed to obtain metadata");
                            } finally {
                                try {
                                    baos.close();
                                    stream.close();
                                } catch (IOException e) {
                                    onError(e);
                                }
                            }
                        }
                    }
                }

                @Override
                public void onError(Throwable throwable) {
                    Log.d(TAG, "onError");
                    throwable.printStackTrace();
                }

                @Override
                public void onComplete() {
                    Log.d(TAG, "onComplete");
                }
            });
}

Retrofit interface:

import io.reactivex.Observable;
import okhttp3.ResponseBody;
import retrofit2.Response;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Streaming;

public interface StreamService {

    @GET("stream")
    @Headers("Icy-MetaData:1")
    @Streaming
    Observable<Response<ResponseBody>> getStreamData();
}
GoranNSW
  • 16
  • 1
  • Thanks for this answer, Goran. Your link for the issue tracker is broken I think. However you put me on the right track for this. I used this library as an inspiration to fetch the metadata using Retrofit and Rx a bit cleaner imho. – timr Nov 10 '17 at 10:58
  • You are right, I corrected the link. I agree that the library is a bit overkill and the simple response fetching would be enough. Will try it by myself when I have time. Please accept the answer. – GoranNSW Nov 10 '17 at 23:17