Add ICY demo application

This commit is contained in:
Sascha Peilicke 2018-10-22 23:40:25 +02:00
parent 95eff3b747
commit 535b40539a
26 changed files with 575 additions and 0 deletions

View File

@ -20,6 +20,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.novoda:bintray-release:0.8.1'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.0.3'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.71'
}
// Workaround for the following test coverage issue. Remove when fixed:
// https://code.google.com/p/android/issues/detail?id=226070

4
demos/icy/README.md Normal file
View File

@ -0,0 +1,4 @@
# ICY demo application #
This folder contains a demo application that showcases the ExoPlayer Shoutcast
Metadata (ICY) extension.

62
demos/icy/build.gradle Normal file
View File

@ -0,0 +1,62 @@
// 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.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion 23
targetSdkVersion project.ext.targetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app does not have translations.
disable 'MissingTranslation'
}
}
dependencies {
implementation project(modulePrefix + 'extension-icy')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:28.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1'
}
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.icydemo">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,160 @@
package com.google.android.exoplayer2.icydemo
import android.net.Uri
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.icy.IcyHttpDataSourceFactory
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.CoroutineStart
import kotlinx.coroutines.experimental.Dispatchers
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.async
import okhttp3.OkHttpClient
/**
* Test application, doesn't necessarily show the best way to do things.
*/
class MainActivity : AppCompatActivity() {
private var exoPlayer: SimpleExoPlayer? = null
private val exoPlayerEventListener = ExoPlayerEventListener()
private lateinit var userAgent: String
private var isPlaying = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
stream.setText(DEFAULT_STREAM)
userAgent = Util.getUserAgent(applicationContext, applicationContext.getString(R.string.app_name))
play_pause.setOnClickListener {
if (isPlaying) {
stop()
play_pause.setImageDrawable(resources.getDrawable(R.drawable.ic_play_arrow_black_24dp, null))
} else {
play()
play_pause.setImageDrawable(resources.getDrawable(R.drawable.ic_stop_black_24dp, null))
}
}
}
private fun play() {
GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, null, {
if (exoPlayer == null) {
exoPlayer = ExoPlayerFactory.newSimpleInstance(applicationContext,
DefaultRenderersFactory(applicationContext),
DefaultTrackSelector(),
DefaultLoadControl()
)
exoPlayer?.addListener(exoPlayerEventListener)
}
val audioAttributes = AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_MUSIC)
.setUsage(C.USAGE_MEDIA)
.build()
exoPlayer?.audioAttributes = audioAttributes
// 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(TAG, "onIcyMetaData: icyHeaders=$icyHeaders")
}
.setIcyMetadataChangeListener { icyMetadata ->
Log.d(TAG, "onIcyMetaData: icyMetadata=$icyMetadata")
}
.build()
// Produces DataSource instances through which media data is loaded
val dataSourceFactory = DefaultDataSourceFactory(
applicationContext, null, icyHttpDataSourceFactory
)
// Produces Extractor instances for parsing the media data
val extractorsFactory = DefaultExtractorsFactory()
// The MediaSource represents the media to be played
val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(extractorsFactory)
.createMediaSource(Uri.parse(stream.text.toString()))
// Prepares media to play (happens on background thread) and triggers
// {@code onPlayerStateChanged} callback when the stream is ready to play
exoPlayer?.prepare(mediaSource)
})
}
private fun stop() {
releaseResources(true)
isPlaying = false
}
private fun releaseResources(releasePlayer: Boolean) {
Log.d(TAG, "releaseResources: releasePlayer=$releasePlayer")
// Stops and releases player (if requested and available).
if (releasePlayer && exoPlayer != null) {
exoPlayer?.release()
exoPlayer?.removeListener(exoPlayerEventListener)
exoPlayer = null
}
}
private inner class ExoPlayerEventListener : Player.EventListener {
override fun onTimelineChanged(timeline: Timeline, manifest: Any?, reason: Int) {
}
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {
}
override fun onLoadingChanged(isLoading: Boolean) {
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
Log.i(TAG, "onPlayerStateChanged: playWhenReady=$playWhenReady, playbackState=$playbackState")
when (playbackState) {
Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY ->
isPlaying = true
Player.STATE_ENDED ->
stop()
}
}
override fun onPlayerError(error: ExoPlaybackException) {
Log.e(TAG, "onPlayerStateChanged: error=$error")
}
override fun onPositionDiscontinuity(reason: Int) {
}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
}
override fun onSeekProcessed() {
}
override fun onRepeatModeChanged(repeatMode: Int) {
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
}
}
companion object {
private const val TAG = "MainActivity"
private const val DEFAULT_STREAM = "http://ice1.somafm.com/indiepop-128-mp3"
}
}

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M8,5v14l11,-7z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,6h12v12H6z"/>
</vector>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.TextInputLayout
android:id="@+id/stream_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<android.support.design.widget.TextInputEditText
android:id="@+id/stream"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/audio_stream"
android:singleLine="true"/>
</android.support.design.widget.TextInputLayout>
</android.support.constraint.ConstraintLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
app:fabSize="normal"
app:srcCompat="@drawable/ic_play_arrow_black_24dp"/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<string name="app_name">Icy-Ext Example</string>
<string name="audio_stream">Audio stream</string>
</resources>

View File

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -20,10 +20,12 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
include modulePrefix + 'demo'
include modulePrefix + 'demo-cast'
include modulePrefix + 'demo-icy'
include modulePrefix + 'demo-ima'
include modulePrefix + 'playbacktests'
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
project(modulePrefix + 'demo-icy').projectDir = new File(rootDir, 'demos/icy')
project(modulePrefix + 'demo-ima').projectDir = new File(rootDir, 'demos/ima')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')