diff --git a/build.gradle b/build.gradle index a013f4fb840..f4eb7704a2b 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/demos/icy/README.md b/demos/icy/README.md new file mode 100644 index 00000000000..77a54098e29 --- /dev/null +++ b/demos/icy/README.md @@ -0,0 +1,4 @@ +# ICY demo application # + +This folder contains a demo application that showcases the ExoPlayer Shoutcast +Metadata (ICY) extension. diff --git a/demos/icy/build.gradle b/demos/icy/build.gradle new file mode 100644 index 00000000000..9ae88c445e9 --- /dev/null +++ b/demos/icy/build.gradle @@ -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' \ No newline at end of file diff --git a/demos/icy/src/main/AndroidManifest.xml b/demos/icy/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..69d64dbf968 --- /dev/null +++ b/demos/icy/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt b/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt new file mode 100644 index 00000000000..643ee84ed79 --- /dev/null +++ b/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt @@ -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" + } +} diff --git a/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml b/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000000..1f6bb290603 --- /dev/null +++ b/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/demos/icy/src/main/res/drawable/ic_launcher_background.xml b/demos/icy/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000000..0d025f9bf6b --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml new file mode 100644 index 00000000000..bf9b895aca9 --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml b/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 00000000000..c428d728dd2 --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/demos/icy/src/main/res/layout/activity_main.xml b/demos/icy/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..14f3598479d --- /dev/null +++ b/demos/icy/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + diff --git a/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000000..eca70cfe52e --- /dev/null +++ b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000000..eca70cfe52e --- /dev/null +++ b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..898f3ed59ac Binary files /dev/null and b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..dffca3601eb Binary files /dev/null and b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/demos/icy/src/main/res/mipmap-mdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..64ba76f75e9 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demos/icy/src/main/res/mipmap-mdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..dae5e082342 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..e5ed46597ea Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..14ed0af3502 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..b0907cac3bf Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..d8ae0315497 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..2c18de9e661 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..beed3cdd2c3 Binary files /dev/null and b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/demos/icy/src/main/res/values/colors.xml b/demos/icy/src/main/res/values/colors.xml new file mode 100644 index 00000000000..e7185c49512 --- /dev/null +++ b/demos/icy/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + + #008577 + #00574B + #D81B60 + + diff --git a/demos/icy/src/main/res/values/strings.xml b/demos/icy/src/main/res/values/strings.xml new file mode 100644 index 00000000000..6f87ce75c73 --- /dev/null +++ b/demos/icy/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Icy-Ext Example + Audio stream + + diff --git a/demos/icy/src/main/res/values/styles.xml b/demos/icy/src/main/res/values/styles.xml new file mode 100644 index 00000000000..5885930df6d --- /dev/null +++ b/demos/icy/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle index d4530d67b74..2e0842fde22 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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')