Skip to content

Commit

Permalink
Add ICY demo application
Browse files Browse the repository at this point in the history
  • Loading branch information
saschpe committed Oct 22, 2018
1 parent 95eff3b commit 535b405
Show file tree
Hide file tree
Showing 26 changed files with 575 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions demos/icy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# ICY demo application #

This folder contains a demo application that showcases the ExoPlayer Shoutcast
Metadata (ICY) extension.
62 changes: 62 additions & 0 deletions demos/icy/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
42 changes: 42 additions & 0 deletions demos/icy/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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"
}
}
34 changes: 34 additions & 0 deletions demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 535b405

Please sign in to comment.