【问题标题】:Extracting metadata from Icecast stream using Exoplayer使用 Exoplayer 从 Icecast 流中提取元数据
【发布时间】:2015-07-19 11:11:50
【问题描述】:

自从从 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);

【问题讨论】:

  • 嗨 Jaz,您可以分享从 mediaplayer 升级到 exoplayer 所做的工作吗?谢谢
  • 嗨 alexistkd,我在下面发布了使用 Exoplayer 的代码。没有关于流元数据的解决方案,但它在播放流时效果很好。
  • 能否在标题中发布元数据?也许元数据在流中,并且有一个像“icy-mettaint:16000”这样的指示符,这就是我对 icecast 流 URL 所拥有的 - AACPlayer 库可以从每隔 16000 字节发送的流中的此类元数据中解码歌曲标题,我我试图弄清楚如何用 ExoPlayer 复制它。
  • 实际上,看起来有人做了一个库github.com/vsmaks/audiostream-metadata-retriever 它会创建一个单独的 URLConnection。也许可以将一些代码调整到您的 ExoPlayer 实现中并保持不变。但是使用这个库将是一个快速的解决方案。
  • 最初我创建了另一个流来读取数据并提取元数据,但担心非蜂窝连接上的用户的数据使用情况。如果这不是问题,则第二个连接有效。

标签: android metadata icecast exoplayer


【解决方案1】:

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

如果您从中获取此流的 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。

【讨论】:

  • 感谢您的回复。我可以在标头中发送对元数据的请求,以便在流中接收它,我可以从流中解析出来,但这也需要单独调用。我希望 Exoplayer 包含一个单独的侦听器,该侦听器将在流中元数据的更改时触发。对元数据流或 API 的单独调用会占用更多用户数据,我发现我正在使用的服务器上的 API 可以但与正在播放的音乐有点不同步。不确定包含元数据是否会在遇到元数据时导致提示。
  • 我对那个播放器不是特别熟悉。如果您正在处理带有直播风格元数据注入的旧格式流,那么您需要确保在解码之前从流中删除元数据,否则您可能会得到垃圾。
  • 刚刚注意到这是一个 Java 而不是 Javascript 的问题。很抱歉造成混乱。
  • 谢谢,您的回答仍然适用于情况的许多方面。我不确定 Exoplayer 是否可以处理包含元数据的流,类似于 IOS 中的 AVPlayer,其中包含您有权访问的元数据对象。
【解决方案2】:

发布以展示对我有用的实现。只是一个具有启动和停止方法以及一些更新 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
    }


}

【讨论】:

  • 这个答案与问题有什么关系?
  • 有人要求我在 cmets 中实现 Exoplayer
【解决方案3】:

我有一个从 IceCast 流启动 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 可以捕获数据并调用回调对象(此处为 playerCallback)。 PlayerCallback 也来自 AACDecoder 库:https://github.com/vbartacek/aacdecoder-android/blob/b58c519a341340a251f3291895c76ff63aef5b94/decoder/src/com/spoledge/aacdecoder/PlayerCallback.java

这样你就不会产生任何重复的流并且它是单一的。如果您不想在项目中包含 AACDecoder 库,则只需复制所需的文件并将它们直接包含在您的项目中即可。

【讨论】:

  • 感谢您发布 st_bk。有机会我会试试的。
  • @st_bk 你能在某处发布完整的源代码吗? ((OkHttpDataSource) uriDataSource).setPlayerCallback 在我尝试复制时是一个未知方法。
  • @codeman 它只是 PlayerCallback 对象的设置器,它是 AACDecoder 库的一部分,它只是设置为传递给 IcyInputStream 构造函数。
  • setter 是在哪里定义的?我在 OkHttpDataSource 中看不到二传手。我从这里得到它:编译'com.google.android.exoplayer:extension-okhttp:r2.0.1'
  • 这似乎不适用于 exoplayer2。 UriDataSource、ExtractorSampleSource 和 MediaCodecAudioTrackRenderer 出现错误。有没有人更新 exoplayer2 的例子?
【解决方案4】:

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

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

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

  1. 从流中解析元数据

第一部分可以在没有基于 ExoPlayer 2.6.1(在 Kotlin 中)的 OkHttp 的情况下完成:

// 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,并在我的 Android 版 Soma FM 流媒体广播应用程序中使用,名为 Alpha+ Player

【讨论】:

    【解决方案5】:

    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")
                }
              }
            }
          }
        })
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-10
      • 2015-04-21
      相关资源
      最近更新 更多