ExoMedia / Exoplayer MP3 元数据提取器
ExoMedia / Exoplayer MP3 metadata extractor
嗨,我正在尝试使用以下流制作一个简单的无线电应用程序:http://lb.topradio.be/topradio.mp3。
目前,为了方便起见,我正在使用 ExoMedia 作为我的默认音频播放器。
它有这个永远不会被调用的 setMetadataListener
方法,因为在 Mp3Extractor
class 中 peekId3Data
方法有这个检查。
if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) {
// Not an ID3 tag.
break;
}
然而,当我在 VLC Media Player 中抛出相同的流时,它能够成功地从流中获取元数据。
这是如何工作的?
我可以用自定义 Extractor
复制它吗?
谢谢
Exoplayer repository 上打开了一个问题。基本上,元数据是通过 headers 传输的,而不是在流本身中。
我在 audiostream-metadata-retriever 上取得了成功,尽管由于流缓存和元数据在实际歌曲开始播放之前到达,我遇到了一些曲目同步问题。
编辑:
RxJava 实现:
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");
}
});
}
改装界面:
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();
}
嗨,我正在尝试使用以下流制作一个简单的无线电应用程序:http://lb.topradio.be/topradio.mp3。
目前,为了方便起见,我正在使用 ExoMedia 作为我的默认音频播放器。
它有这个永远不会被调用的 setMetadataListener
方法,因为在 Mp3Extractor
class 中 peekId3Data
方法有这个检查。
if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) {
// Not an ID3 tag.
break;
}
然而,当我在 VLC Media Player 中抛出相同的流时,它能够成功地从流中获取元数据。
这是如何工作的?
我可以用自定义 Extractor
复制它吗?
谢谢
Exoplayer repository 上打开了一个问题。基本上,元数据是通过 headers 传输的,而不是在流本身中。
我在 audiostream-metadata-retriever 上取得了成功,尽管由于流缓存和元数据在实际歌曲开始播放之前到达,我遇到了一些曲目同步问题。
编辑:
RxJava 实现:
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");
}
});
}
改装界面:
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();
}