Commit 95eff3b7 by Sascha Peilicke

Add Shoutcast Metadata Protocol (ICY) extension

Based on the OkHttp extension.

Resolves #3735
parent 1ef3efaa
......@@ -28,6 +28,7 @@ include modulePrefix + 'testutils-robolectric'
include modulePrefix + 'extension-ffmpeg'
include modulePrefix + 'extension-flac'
include modulePrefix + 'extension-gvr'
include modulePrefix + 'extension-icy'
include modulePrefix + 'extension-ima'
include modulePrefix + 'extension-cast'
include modulePrefix + 'extension-cronet'
......@@ -50,6 +51,7 @@ project(modulePrefix + 'testutils-robolectric').projectDir = new File(rootDir, '
project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg')
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')
project(modulePrefix + 'extension-icy').projectDir = new File(rootDir, 'extensions/icy')
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima')
project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast')
project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet')
......
# ExoPlayer Shoutcast Metadata Protocol (ICY) extension #
The Shoutcast Metadata Protocol extension provides **IcyHttpDataSource** and
**IcyHttpDataSourceFactory** which can parse ICY metadata information such as
stream name and genre as well as current song information from a music stream.
You can find the protocol description here:
- https://cast.readme.io/v1.0/docs/icy
- http://www.smackfu.com/stuff/programming/shoutcast.html
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
```gradle
implementation 'com.google.android.exoplayer:extension-icy:2.X.X'
```
where `2.X.X` is the version, which must match the version of the ExoPlayer
library being used.
Alternatively, you can clone the ExoPlayer repository and depend on the module
locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Using the extension ##
To receive information about the current music stream (such as name and genre,
see **IcyHeaders** class) as well as current song information (see
**IcyMetadata** class), pass an instance of **IcyHttpDataSourceFactory** instead
of an **DefaultHttpDataSourceFactory** like this (in Kotlin):
```kotlin
// ... exoPlayer instance already created
// Custom HTTP data source factory which requests Icy metadata and parses it if
// the stream server supports it
val client = OkHttpClient.Builder().build()
val icyHttpDataSourceFactory = IcyHttpDataSourceFactory.Builder(client)
.setUserAgent(userAgent)
.setIcyHeadersListener { icyHeaders ->
Log.d("XXX", "onIcyHeaders: %s".format(icyHeaders.toString()))
}
.setIcyMetadataChangeListener { icyMetadata ->
Log.d("XXX", "onIcyMetaData: %s".format(icyMetadata.toString()))
}
.build()
// Produces DataSource instances through which media data is loaded
val dataSourceFactory = DefaultDataSourceFactory(applicationContext, null, icyHttpDataSourceFactory)
// The MediaSource represents the media to be played
val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(DefaultExtractorsFactory())
.createMediaSource(sourceUri)
// exoPlayer?.prepare(mediaSource) ...
```
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.icy.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt'
}
}
dependencies {
implementation project(modulePrefix + 'extension-okhttp')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
api 'com.google.android.exoplayer:extension-okhttp:' + releaseVersion
testImplementation 'junit:junit:' + junitVersion
testImplementation project(modulePrefix + 'testutils-robolectric')
}
ext {
javadocTitle = 'Shoutcast Metadata Protocol (ICY) extension'
}
apply from: '../../javadoc_library.gradle'
ext {
releaseArtifact = 'extension-icy'
releaseDescription = 'Shoutcast Metadata Protocol (ICY) extension for ExoPlayer.'
}
apply from: '../../publish.gradle'
# Proguard rules specific to the Icy extension.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest package="com.google.android.exoplayer2.ext.icy"/>
package com.google.android.exoplayer2.ext.icy;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Predicate;
import okhttp3.CacheControl;
import okhttp3.Call;
/**
* A {@link HttpDataSource.Factory} that produces {@link IcyHttpDataSource} instances.
*/
public final class IcyHttpDataSourceFactory extends OkHttpDataSource.BaseFactory {
private Call.Factory callFactory;
private String userAgent;
private Predicate<String> contentTypePredicate;
private CacheControl cacheControl;
private IcyHttpDataSource.IcyHeadersListener icyHeadersListener;
private IcyHttpDataSource.IcyMetadataListener icyMetadataListener;
private IcyHttpDataSourceFactory() {
// See class Builder
}
/**
* Constructs a IcyHttpDataSourceFactory.
*/
public final static class Builder {
private final IcyHttpDataSourceFactory factory;
public Builder(@NonNull Call.Factory callFactory) {
// Apply defaults
factory = new IcyHttpDataSourceFactory();
factory.callFactory = callFactory;
}
public Builder setUserAgent(@NonNull final String userAgent) {
factory.userAgent = userAgent;
return this;
}
public Builder setContentTypePredicate(@NonNull final Predicate<String> contentTypePredicate) {
factory.contentTypePredicate = contentTypePredicate;
return this;
}
public Builder setCacheControl(@NonNull final CacheControl cacheControl) {
factory.cacheControl = cacheControl;
return this;
}
public Builder setIcyHeadersListener(
@NonNull final IcyHttpDataSource.IcyHeadersListener icyHeadersListener) {
factory.icyHeadersListener = icyHeadersListener;
return this;
}
public Builder setIcyMetadataChangeListener(
@NonNull final IcyHttpDataSource.IcyMetadataListener icyMetadataListener) {
factory.icyMetadataListener = icyMetadataListener;
return this;
}
public IcyHttpDataSourceFactory build() {
return factory;
}
}
@Override
protected IcyHttpDataSource createDataSourceInternal(
@NonNull HttpDataSource.RequestProperties defaultRequestProperties) {
return new IcyHttpDataSource.Builder(callFactory)
.setUserAgent(userAgent)
.setContentTypePredicate(contentTypePredicate)
.setCacheControl(cacheControl)
.setDefaultRequestProperties(defaultRequestProperties)
.setIcyHeadersListener(icyHeadersListener)
.setIcyMetadataListener(icyMetadataListener)
.build();
}
}
package com.google.android.exoplayer2.ext.icy;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import okhttp3.OkHttpClient;
import com.google.android.exoplayer2.ext.icy.test.Constants;
import static org.junit.Assert.assertNotNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public final class IcyHttpDataSourceFactoryTest {
private final IcyHttpDataSource.IcyHeadersListener TEST_ICY_HEADERS_LISTENER = icyHeaders -> {
};
private final IcyHttpDataSource.IcyMetadataListener TEST_ICY_METADATA_LISTENER = icyMetadata -> {
};
@Test
public void createDataSourceViaFactoryFromFactoryBuilder() {
// Arrange
OkHttpClient client = new OkHttpClient.Builder().build();
IcyHttpDataSourceFactory factory = new IcyHttpDataSourceFactory.Builder(client)
.setUserAgent(Constants.TEST_USER_AGENT)
.setIcyHeadersListener(TEST_ICY_HEADERS_LISTENER)
.setIcyMetadataChangeListener(TEST_ICY_METADATA_LISTENER)
.build();
HttpDataSource.RequestProperties requestProperties = new HttpDataSource.RequestProperties();
// Act
IcyHttpDataSource source = factory.createDataSourceInternal(requestProperties);
// Assert
assertNotNull(source);
}
}
package com.google.android.exoplayer2.ext.icy;
import com.google.android.exoplayer2.ext.icy.IcyHttpDataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import okhttp3.OkHttpClient;
import com.google.android.exoplayer2.ext.icy.test.Constants;
import static org.junit.Assert.assertNotNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public final class IcyHttpDataSourceTest {
@Test
public void createDataSourceFromBuilder() {
// Arrange, act
OkHttpClient client = new OkHttpClient.Builder().build();
IcyHttpDataSource source = new IcyHttpDataSource.Builder(client)
.setUserAgent(Constants.TEST_USER_AGENT)
.build();
// Assert
assertNotNull(source);
}
}
package com.google.android.exoplayer2.ext.icy.test;
public final class Constants {
public static final String TEST_USER_AGENT = "test-agent";
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment