使用Exoplayer从Icecast流中提取元数据

时间:2015-05-08 13:47:32

标签: android metadata icecast exoplayer

自从从Mediaplayer切换到一个简单的实现Exoplayer后,我注意到了很多改进的加载时间,但我想知道在流式传输音频时是否有内置的功能,例如元数据更改监听器?

我使用一个简单的例子实现了Exoplayer,如下所示:

    Uri uri = Uri.parse(url);
    DefaultSampleSource sampleSource =
            new DefaultSampleSource(new FrameworkSampleExtractor(context, uri, null), 2);
    TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
    mExoPlayerInstance.prepare(audioRenderer);
    mExoPlayerInstance.setPlayWhenReady(true);

5 个答案:

答案 0 :(得分:4)

我有一个从IceCast Stream启动ExoPlayer的AsyncTask:

OkHttpClient okHttpClient = new OkHttpClient();

UriDataSource uriDataSource = new OkHttpDataSource(okHttpClient, userAgent, null, null, CacheControl.FORCE_NETWORK);
((OkHttpDataSource) uriDataSource).setRequestProperty("Icy-MetaData", "1");
((OkHttpDataSource) uriDataSource).setPlayerCallback(mPlayerCallback);

DataSource dataSource = new DefaultUriDataSource(context, null, uriDataSource);

ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
                    BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);


MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
MediaCodecSelector.DEFAULT, null, true, null, null,
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
mPlayerCallback.playerStarted();
exoPlayer.prepare(audioRenderer);

OkHttpDataSource是使用OkHttpClient实现HttpDataSource的类。它创建InputStream作为请求的响应。我在AACDecoder库https://github.com/vbartacek/aacdecoder-android/blob/master/decoder/src/com/spoledge/aacdecoder/IcyInputStream.java中包含了这个类,并根据响应将InputStream替换为IcyInputStream:

(在OkHttpDataSource的open()中)

try {
  response = okHttpClient.newCall(request).execute();
  responseByteStream = response.body().byteStream();

  String icyMetaIntString = response.header("icy-metaint");
  int icyMetaInt = -1;
  if (icyMetaIntString != null) {
    try {
      icyMetaInt = Integer.parseInt(icyMetaIntString);
      if (icyMetaInt > 0)
        responseByteStream = new IcyInputStream(responseByteStream, icyMetaInt, playerCallback);
    } catch (Exception e) {
      Log.e(TAG, "The icy-metaint '" + icyMetaInt + "' cannot be parsed: '" + e);
    }
  }

} catch (IOException e) {
  throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
      dataSpec);
}

现在IcyInputStream可以捕获medatada并调用回调对象(此处为playerCallback)。 PlayerCallback也来自AACDecoder库:https://github.com/vbartacek/aacdecoder-android/blob/b58c519a341340a251f3291895c76ff63aef5b94/decoder/src/com/spoledge/aacdecoder/PlayerCallback.java

这样你就不会制作任何重复的流而且它是单数的。如果您不想在项目中安装AACDecoder库,那么您只需复制所需的文件并将其直接包含在项目中。

答案 1 :(得分:0)

这取决于几个因素(如流格式),但简短的答案是否定的。大多数浏览器都不公开这个。但是有一种带外元数据方法。

如果您从中获取此流的Icecast服务器运行的是2.4.1或更高版本,那么您可以从其JSON API though查询元数据。基本上通过查询http://icecast.example.org/status.json或者您只想要一个特定流的信息:http://icecast.example.org/status.json?mount=/stream.ogg

这可以用于旧版本的Icecast,但是API输出需要由托管网页/播放器的网络服务器或CORS ACAO支持进行缓存。

答案 2 :(得分:0)

发布以显示适合我的实施。只是一个具有启动和停止方法的单身人士以及一些更新UI的意图。

private void startStation(Station station){
if(station!=null) {
  ExoPlayerSingleton.getInstance();
  ExoPlayerSingleton.playStation(station, getApplicationContext());
 }
}


public class ExoPlayerSingleton {

private static ExoPlayer mExoPlayerInstance;
private static MediaCodecAudioTrackRenderer audioRenderer;
private static final int BUFFER_SIZE = 10 * 1024 * 1024;
private static MediaPlayer mediaPlayer;
public static synchronized ExoPlayer getInstance() {


    if (mExoPlayerInstance == null) {
        mExoPlayerInstance = ExoPlayer.Factory.newInstance(1);
    }

    return mExoPlayerInstance;
}

 public static synchronized ExoPlayer getCurrentInstance() {
    return mExoPlayerInstance;
}

public static void  stopExoForStation(Context context){

    if(mExoPlayerInstance!=null) {
        try {

            mExoPlayerInstance.stop();
            mExoPlayerInstance.release();
            mExoPlayerInstance = null;
            Intent intent = new Intent();
            intent.setAction("com.zzz.now_playing_receiver");
            context.sendBroadcast(intent);
        } catch (Exception e) {
            Log.e("Exoplayer Error", e.toString());
        }

    }
}


public static boolean isPlaying(){

    if(mExoPlayerInstance!=null &&(mExoPlayerInstance.getPlaybackState()==       ExoPlayer.STATE_READY )){
        return true;
    }else{
        return false;
    }
}

public static boolean isBuffering(){

    if(mExoPlayerInstance!=null &&(mExoPlayerInstance.getPlaybackState()== ExoPlayer.STATE_BUFFERING)){
        return true;
    }else{
        return false;
    }
}

public static boolean isPreparing(){

    if(mExoPlayerInstance!=null &&( mExoPlayerInstance.getPlaybackState()== ExoPlayer.STATE_PREPARING)){
        return true;
    }else{
        return false;
    }
}

public static void playStation(Station station,final Context context){

    getInstance();
    url = station.getLow_Stream();

    if(url!=null) {
        Uri uri = Uri.parse(url);
        String userAgent = Util.getUserAgent(context, "SomeRadio");
        DataSource audioDataSource = new DefaultUriDataSource(context,userAgent);
        Mp3Extractor extractor = new Mp3Extractor();
                ExtractorSampleSource sampleSource = new ExtractorSampleSource(
                uri, audioDataSource,BUFFER_SIZE, extractor );

        audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);


        mExoPlayerInstance.addListener(new ExoPlayer.Listener() {
            @Override
            public void onPlayerStateChanged(boolean b, int i) {

                if (i == ExoPlayer.STATE_BUFFERING) {


                } else if (i == ExoPlayer.STATE_IDLE) {

                } else if (i == ExoPlayer.STATE_ENDED) {


                } else if (i == ExoPlayer.STATE_READY) {
                    Intent intent = new Intent();
                    intent.setAction("com.zzz.pause_play_update");
                    context.sendBroadcast(intent);

                    Intent progress_intent = new Intent();
                    progress_intent.putExtra("show_dialog", false);
                    progress_intent.setAction("com.zzz.load_progess");
                    context.sendBroadcast(progress_intent);
                }


            }

            @Override
            public void onPlayWhenReadyCommitted() {

                 }

            @Override
            public void onPlayerError(ExoPlaybackException e) {
                String excep =  e.toString();
                Log.e("ExoPlayer Error",excep);

            }
        });
        mExoPlayerInstance.prepare(audioRenderer);
        mExoPlayerInstance.setPlayWhenReady(true);

    }else{
        //send intent to raise no connection dialog
    }


}

答案 3 :(得分:0)

解析Shoutcast Metadata Protocol由两部分组成:

  1. 通过发送HTTP-Header Icy-Metadata:1 告诉服务器您的客户端支持元数据,例如:
  2.   

    curl -v -H“Icy-MetaData:1”http://ice1.somafm.com/defcon-128-mp3

    1. 解析流中的元数据
    2. 第一部分可以在没有基于ExoPlayer 2.6.1的OkHttp的情况下完成(在Kotlin中):

      // Custom HTTP data source factory with IceCast metadata HTTP header set
      val defaultHttpDataSourceFactory = DefaultHttpDataSourceFactory(userAgent, null)
      defaultHttpDataSourceFactory.setDefaultRequestProperty("Icy-MetaData", "1")
      
      // Produces DataSource instances through which media data is loaded.
      val dataSourceFactory = DefaultDataSourceFactory(
          applicationContext, null, defaultHttpDataSourceFactory)
      

      第二部分涉及更多,并且发布所有代码有点太多了。您可能想要查看我创建的ExoPlayer2扩展程序:

      github.com/saschpe/android-exoplayer2-ext-icy

      它不依赖于OkHttp,而是用于我的称为Alpha+ Player的Android Soma FM流媒体广播应用程序。

答案 4 :(得分:0)

冰冷的元数据支持现已在exoplayer版本2.10中提供:

ExoPlayerFactory.newSimpleInstance(this).apply {
    setAudioAttributes(
      AudioAttributes.Builder()
        .setContentType(C.CONTENT_TYPE_MUSIC)
        .setUsage(C.USAGE_MEDIA)
        .build(), true
    )
    addMetadataOutput(object : MetadataOutput {
      override fun onMetadata(metadata: Metadata) {
        for (n in 0 until metadata.length()) {
          when (val md = metadata[n]) {
            is com.google.android.exoplayer2.metadata.icy.IcyInfo -> {
              Log.d(TAG, "Title: ${md.title} URL: ${md.url}")
            }
            else -> {
              Log.d(TAG, "Some other sort of metadata: $md")
            }
          }
        }
      }
    })
  }