diff --git a/build.gradle b/build.gradle index 8c2186522ae..a03f1e0c133 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.novoda:bintray-release:0.9.1' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.1' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' } } allprojects { diff --git a/core_settings.gradle b/core_settings.gradle index 241b94a19ba..9cf91d379b5 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -49,6 +49,7 @@ include modulePrefix + 'extension-rtmp' include modulePrefix + 'extension-leanback' include modulePrefix + 'extension-jobdispatcher' include modulePrefix + 'extension-workmanager' +include modulePrefix + 'extension-multi-track' project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all') project(modulePrefix + 'library-common').projectDir = new File(rootDir, 'library/common') @@ -78,3 +79,4 @@ project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensi project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback') project(modulePrefix + 'extension-jobdispatcher').projectDir = new File(rootDir, 'extensions/jobdispatcher') project(modulePrefix + 'extension-workmanager').projectDir = new File(rootDir, 'extensions/workmanager') +project(modulePrefix + 'extension-multi-track').projectDir = new File(rootDir, 'extensions/multi-track') diff --git a/demos/multi-track/.gitignore b/demos/multi-track/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/demos/multi-track/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/demos/multi-track/build.gradle b/demos/multi-track/build.gradle new file mode 100644 index 00000000000..966a9c38f45 --- /dev/null +++ b/demos/multi-track/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} +apply from: '../../constants.gradle' + +android { + compileSdkVersion 32 + + defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.appTargetSdkVersion + multiDexEnabled true + } + + buildTypes { + release { + shrinkResources true + minifyEnabled true + proguardFiles = [ + "proguard-rules.txt", + getDefaultProguardFile('proguard-android.txt') + ] + } + debug { + jniDebuggable = true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'library-ui') + implementation project(modulePrefix + 'extension-cast') + implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion + implementation 'androidx.multidex:multidex:' + androidxMultidexVersion + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'com.google.android.material:material:1.2.1' + + implementation 'com.google.code.gson:gson:2.8.6' + + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' +} \ No newline at end of file diff --git a/demos/multi-track/proguard-rules.pro b/demos/multi-track/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/demos/multi-track/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/demos/multi-track/src/androidTest/java/com/github/qingmei2/exoplayer/multi_track/ExampleInstrumentedTest.kt b/demos/multi-track/src/androidTest/java/com/github/qingmei2/exoplayer/multi_track/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..a32d33ee263 --- /dev/null +++ b/demos/multi-track/src/androidTest/java/com/github/qingmei2/exoplayer/multi_track/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.github.qingmei2.exoplayer.multi_track + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.github.qingmei2.exoplayer.multi_track", appContext.packageName) + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/AndroidManifest.xml b/demos/multi-track/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..5e0904c3f17 --- /dev/null +++ b/demos/multi-track/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/multi-track/src/main/assets/media/eh_eh/BGV.wav b/demos/multi-track/src/main/assets/media/eh_eh/BGV.wav new file mode 100644 index 00000000000..70022a17af4 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/eh_eh/BGV.wav differ diff --git a/demos/multi-track/src/main/assets/media/eh_eh/Bass.wav b/demos/multi-track/src/main/assets/media/eh_eh/Bass.wav new file mode 100644 index 00000000000..446461ce507 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/eh_eh/Bass.wav differ diff --git a/demos/multi-track/src/main/assets/media/eh_eh/Drums.wav b/demos/multi-track/src/main/assets/media/eh_eh/Drums.wav new file mode 100644 index 00000000000..c71f92a5de3 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/eh_eh/Drums.wav differ diff --git a/demos/multi-track/src/main/assets/media/eh_eh/Keys.wav b/demos/multi-track/src/main/assets/media/eh_eh/Keys.wav new file mode 100644 index 00000000000..d9113322bb2 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/eh_eh/Keys.wav differ diff --git a/demos/multi-track/src/main/assets/media/eh_eh/Lead.wav b/demos/multi-track/src/main/assets/media/eh_eh/Lead.wav new file mode 100644 index 00000000000..2a2adb64960 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/eh_eh/Lead.wav differ diff --git a/demos/multi-track/src/main/assets/media/half_moon.mp3 b/demos/multi-track/src/main/assets/media/half_moon.mp3 new file mode 100644 index 00000000000..38d6fd64080 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/half_moon.mp3 differ diff --git a/demos/multi-track/src/main/assets/media/songs.json b/demos/multi-track/src/main/assets/media/songs.json new file mode 100644 index 00000000000..384c2d52bd0 --- /dev/null +++ b/demos/multi-track/src/main/assets/media/songs.json @@ -0,0 +1,48 @@ +[ + { + "songName": "Eh,Eh(Nothing Else I Can Say)", + "singerName": "Lady Gaga", + "partItems": [ + { + "partId": "1", + "partName": "贝斯", + "partType": "instrument", + "partPath": "asset:///media/eh_eh/Bass.wav", + "mainType": false, + "defaultPlay": true + }, + { + "partId": "2", + "partName": "和声", + "partType": "harmony", + "partPath": "asset:///media/eh_eh/BGV.wav", + "mainType": false, + "defaultPlay": true + }, + { + "partId": "3", + "partName": "鼓音", + "partType": "instrument", + "partPath": "asset:///media/eh_eh/Drums.wav", + "mainType": false, + "defaultPlay": true + }, + { + "partId": "4", + "partName": "背景音", + "partType": "background", + "partPath": "asset:///media/eh_eh/Keys.wav", + "mainType": false, + "defaultPlay": true + }, + { + "partId": "5", + "partName": "歌手", + "partType": "musician", + "partPath": "asset:///media/eh_eh/Lead.wav", + "mainType": true, + "defaultPlay": true + } + ] + } +] \ No newline at end of file diff --git a/demos/multi-track/src/main/assets/media/summer.mp3 b/demos/multi-track/src/main/assets/media/summer.mp3 new file mode 100644 index 00000000000..e65da895542 Binary files /dev/null and b/demos/multi-track/src/main/assets/media/summer.mp3 differ diff --git a/demos/multi-track/src/main/assets/media/sunny.mp4 b/demos/multi-track/src/main/assets/media/sunny.mp4 new file mode 100644 index 00000000000..7c0a2b860ee Binary files /dev/null and b/demos/multi-track/src/main/assets/media/sunny.mp4 differ diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/MainActivity.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/MainActivity.kt new file mode 100644 index 00000000000..c6a05af69a7 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/MainActivity.kt @@ -0,0 +1,28 @@ +package com.github.qingmei2.exoplayer.multi_track + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import com.github.qingmei2.exoplayer.multi_track.ui.MultiMusicActivity +import com.github.qingmei2.exoplayer.multi_track.ui.MultiTrackMainActivity + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // 同时播放多首音乐 + findViewById(R.id.btn_multi_music).setOnClickListener(this::onMultiPlayerClicked) + // 同时播放单曲多个音轨文件 + findViewById(R.id.btn_single_song_list).setOnClickListener(this::onSingleSongListClicked) + } + + private fun onMultiPlayerClicked(view: View) { + MultiMusicActivity.launch(this) + } + + private fun onSingleSongListClicked(view: View) { + MultiTrackMainActivity.launch(this) + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/common/SimpleSeekBarListener.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/common/SimpleSeekBarListener.kt new file mode 100644 index 00000000000..c7be9c7de88 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/common/SimpleSeekBarListener.kt @@ -0,0 +1,22 @@ +package com.github.qingmei2.exoplayer.multi_track.common + +import android.widget.SeekBar +import androidx.annotation.RestrictTo +import com.google.android.exoplayer2.Player + +@RestrictTo(RestrictTo.Scope.LIBRARY) +class SimpleSeekBarListener(private val player: Player) : SeekBar.OnSeekBarChangeListener { + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (progress == 0 || seekBar.max == 0) return + if (fromUser) { + player.seekTo(progress * 1000L) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/entity/SongItem.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/entity/SongItem.kt new file mode 100644 index 00000000000..f62370671f5 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/entity/SongItem.kt @@ -0,0 +1,15 @@ +package com.github.qingmei2.exoplayer.multi_track.entity + +data class SongItem(val songName: String, + val singerName: String, + val partItems: List) + + +data class SongPartItem( + val partId: String, + val partName: String, + val partType: String, + val partPath: String, + val mainType: Boolean, + val defaultPlay: Boolean +) \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/IPartItemController.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/IPartItemController.kt new file mode 100644 index 00000000000..f6581e58291 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/IPartItemController.kt @@ -0,0 +1,100 @@ +package com.github.qingmei2.exoplayer.multi_track.ui + +import android.content.Context +import android.net.Uri +import android.os.Handler +import android.os.Looper +import androidx.annotation.IntRange +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import com.github.qingmei2.exoplayer.multi_track.entity.SongPartItem +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.SimpleExoPlayer +import java.util.* + +interface IPartItemController : LifecycleEventObserver { + + fun onBindItem(pos: Int, trackItem: SongPartItem, switchCallback: OnTrackSwitchChangedCallback) + + fun isTrackOpen(): Boolean + + fun setTrackOpen(isOpen: Boolean) + + fun onSeek(progress: Int, byUser: Boolean = false) + + fun onClickPlay() + + fun onClickPause() +} + +typealias OnTrackSwitchChangedCallback = (Boolean) -> Unit + +class SinglePartItemController(context: Context, + private val position: Int, + private val item: SongPartItem) : IPartItemController { + + + private val mExoPlayer: SimpleExoPlayer + + private var mSwitchChangedCallback: OnTrackSwitchChangedCallback? = null + + private var isTrackOpen: Boolean = true + + private val mHandler = Handler(Looper.myLooper()!!) + + init { + mExoPlayer = SimpleExoPlayer.Builder(context).build() + .apply { + addMediaItem(MediaItem.fromUri(Uri.parse(item.partPath))) + prepare() + } + } + + override fun onBindItem(pos: Int, + trackItem: SongPartItem, + switchCallback: OnTrackSwitchChangedCallback) { + this.mSwitchChangedCallback = switchCallback + } + + override fun setTrackOpen(isOpen: Boolean) { + isTrackOpen = isOpen + if (!mExoPlayer.isPlaying) { + mExoPlayer.play() + mSwitchChangedCallback?.invoke(true) + } else { + mExoPlayer.pause() + mSwitchChangedCallback?.invoke(false) + } + } + + override fun isTrackOpen(): Boolean { + return isTrackOpen + } + + override fun onSeek(@IntRange(from = 0, to = 100) progress: Int, byUser: Boolean) { + mExoPlayer.volume = progress / 100f + } + + override fun onClickPlay() { + if (!mExoPlayer.isPlaying) { + mExoPlayer.play() + } + } + + override fun onClickPause() { + if (mExoPlayer.isPlaying) { + mExoPlayer.pause() + } + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_DESTROY -> { + mExoPlayer.stop() + mExoPlayer.release() + } + } + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiMusicActivity.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiMusicActivity.kt new file mode 100644 index 00000000000..96e6daa57b5 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiMusicActivity.kt @@ -0,0 +1,154 @@ +package com.github.qingmei2.exoplayer.multi_track.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.SeekBar +import androidx.appcompat.app.AppCompatActivity +import com.github.qingmei2.exoplayer.multi_track.R +import com.github.qingmei2.exoplayer.multi_track.common.SimpleSeekBarListener +import com.github.qingmei2.exoplayer.multi_track.utils.DemoDataSources +import com.google.android.exoplayer2.DefaultRenderersFactory +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.ui.StyledPlayerView +import java.util.* + +/** + * 页面同时播放多首音乐 + */ +class MultiMusicActivity : AppCompatActivity() { + + companion object Launcher { + + fun launch(context: Context) { + val intent = Intent(context, MultiMusicActivity::class.java) + context.startActivity(intent) + } + } + + private lateinit var mBtnPlayTrack1: View + private lateinit var mBtnPlayTrack2: View + + private lateinit var mSeekBar1: SeekBar + private lateinit var mSeekBar2: SeekBar + + private lateinit var mExoPlayer1: ExoPlayer + private lateinit var mExoPlayer2: ExoPlayer + + private lateinit var mExoVideoView: StyledPlayerView + private lateinit var mExoVideoPlayer: ExoPlayer + + private var mTimer: Timer? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_multi_player) + + mBtnPlayTrack1 = findViewById(R.id.btn_play_track1) + mBtnPlayTrack2 = findViewById(R.id.btn_play_track2) + + mSeekBar1 = findViewById(R.id.seek_bar_01) + mSeekBar2 = findViewById(R.id.seek_bar_02) + + mExoVideoView = findViewById(R.id.player_view) + + initVideoPlayer() + initAudioPlayer() + initSeekbars() + } + + private fun initVideoPlayer() { + mExoVideoPlayer = SimpleExoPlayer.Builder(this).build() + .apply { + mExoVideoView.player = this + addMediaItem(DemoDataSources.Assets.SUNNY_MP4) + prepare() + } + } + + private fun initAudioPlayer() { + mExoPlayer1 = SimpleExoPlayer.Builder(this, DefaultRenderersFactory(this)).build() + .apply { + addMediaItem(DemoDataSources.Assets.SUMMER_MP3) + prepare() + } + + mExoPlayer2 = SimpleExoPlayer.Builder(this, DefaultRenderersFactory(this)).build() + .apply { + addMediaItem(DemoDataSources.Assets.HALF_MOON_MP3) + prepare() + } + + mBtnPlayTrack1.setOnClickListener { + if (mExoPlayer1.isPlaying) { + mExoPlayer1.pause() + } else { + mExoPlayer1.play() + } + } + + mBtnPlayTrack2.setOnClickListener { + if (mExoPlayer2.isPlaying) { + mExoPlayer2.pause() + } else { + mExoPlayer2.play() + } + } + } + + private fun initSeekbars() { + mSeekBar1.setOnSeekBarChangeListener(SimpleSeekBarListener(mExoPlayer1)) + mSeekBar2.setOnSeekBarChangeListener(SimpleSeekBarListener(mExoPlayer2)) + } + + private fun startTimer() { + stopTimer() + mTimer = Timer().apply { + scheduleAtFixedRate(object : TimerTask() { + override fun run() { + mSeekBar1.post { + val durationSec = mExoPlayer1.duration / 1000L + val curDurationSec = mExoPlayer1.currentPosition / 1000L + mSeekBar1.max = durationSec.toInt() + mSeekBar1.progress = curDurationSec.toInt() + } + + mSeekBar2.post { + val durationSec1 = mExoPlayer2.duration / 1000L + val curDurationSec1 = mExoPlayer2.currentPosition / 1000L + mSeekBar2.max = durationSec1.toInt() + mSeekBar2.progress = curDurationSec1.toInt() + } + } + }, 1000L, 1000L) + } + } + + private fun stopTimer() { + mTimer?.apply { + cancel() + purge() + } + mTimer = null + } + + override fun onResume() { + super.onResume() + startTimer() + } + + override fun onPause() { + super.onPause() + stopTimer() + } + + override fun onDestroy() { + super.onDestroy() + mExoPlayer1.stop() + mExoPlayer1.release() + mExoPlayer2.stop() + mExoPlayer2.release() + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackListAdapter.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackListAdapter.kt new file mode 100644 index 00000000000..dec00274026 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackListAdapter.kt @@ -0,0 +1,133 @@ +package com.github.qingmei2.exoplayer.multi_track.ui + +import android.content.Context +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.SeekBar +import android.widget.TextView +import androidx.annotation.MainThread +import androidx.collection.ArrayMap +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.github.qingmei2.exoplayer.multi_track.R +import com.github.qingmei2.exoplayer.multi_track.entity.SongPartItem +import com.google.android.material.switchmaterial.SwitchMaterial + +class MultiTrackListAdapter( + diffUtil: DiffUtil.ItemCallback = MultiTrackListDiffUtil +) : ListAdapter(diffUtil), LifecycleEventObserver { + + private val mTrackControllers = ArrayMap() + + @MainThread + fun bindItems(items: List) { + this.submitList(items) + } + + @MainThread + fun onClickPlay() { + mTrackControllers.forEach { entry -> + entry.value.onClickPlay() + } + } + + @MainThread + fun onClickPause() { + mTrackControllers.forEach { entry -> + entry.value.onClickPause() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiTrackListViewHolder { + return LayoutInflater.from(parent.context) + .inflate(R.layout.layout_listitem_song_track, parent, false) + .let { MultiTrackListViewHolder(it) } + } + + override fun onBindViewHolder(holder: MultiTrackListViewHolder, position: Int) { + val item = getItem(position) + val controller = getTrackController(holder.itemView.context, position, item) + holder.binds(position, item, controller) + } + + private fun getTrackController(context: Context, pos: Int, item: SongPartItem): IPartItemController { + return when (mTrackControllers[pos] == null) { + true -> { + SinglePartItemController(context, pos, item).also { + mTrackControllers[pos] = it + } + } + false -> mTrackControllers[pos]!! + } + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + mTrackControllers.forEach { entry -> + entry.value.onStateChanged(source, event) + } + } +} + +private object MultiTrackListDiffUtil : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: SongPartItem, newItem: SongPartItem): Boolean { + return oldItem.partId == newItem.partId + } + + override fun areContentsTheSame(oldItem: SongPartItem, newItem: SongPartItem): Boolean { + return oldItem == newItem + } +} + +class MultiTrackListViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + private val seekbar: SeekBar + private val trackName: TextView + private val trackSwitch: SwitchMaterial + + init { + seekbar = view.findViewById(R.id.seek_bar) + trackName = view.findViewById(R.id.tv_track_name) + trackSwitch = view.findViewById(R.id.swt_track) + } + + fun binds(pos: Int, trackItem: SongPartItem, + controller: IPartItemController) { + val isTrackOpen = controller.isTrackOpen() + this.onTrackOpenChanged(isTrackOpen) + + trackName.text = trackItem.partName + trackSwitch.isChecked = isTrackOpen + trackSwitch.setOnCheckedChangeListener { _, isChecked -> + controller.setTrackOpen(isChecked) + } + + seekbar.progress = 100 + seekbar.max = 100 + seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, byUser: Boolean) { + controller.onSeek(progress, byUser) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + } + }) + + val switchCallback: OnTrackSwitchChangedCallback = this::onTrackOpenChanged + + controller.onBindItem(pos, trackItem, switchCallback) + } + + private fun onTrackOpenChanged(isTrackOpen: Boolean) { + trackName.setTextColor(if (isTrackOpen) Color.BLACK else Color.GRAY) + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackMainActivity.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackMainActivity.kt new file mode 100644 index 00000000000..fb8107e37d1 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/ui/MultiTrackMainActivity.kt @@ -0,0 +1,60 @@ +package com.github.qingmei2.exoplayer.multi_track.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.widget.Button +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.github.qingmei2.exoplayer.multi_track.MainActivity +import com.github.qingmei2.exoplayer.multi_track.R +import com.github.qingmei2.exoplayer.multi_track.utils.DemoDataSources +import java.util.* + +/** + * 同时播放单曲多个音轨文件 + */ +class MultiTrackMainActivity : AppCompatActivity() { + + companion object Launcher { + + fun launch(context: Context) { + val intent = Intent(context, MultiTrackMainActivity::class.java) + context.startActivity(intent) + } + } + + private lateinit var mRecyclerView: RecyclerView + private lateinit var mListAdapter: MultiTrackListAdapter + + private lateinit var mBtnPlay: Button + private lateinit var mBtnPause: Button + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_multi_track) + + mRecyclerView = findViewById(R.id.recyclerview) + mBtnPlay = findViewById(R.id.btn_play) + mBtnPause = findViewById(R.id.btn_pause) + + initList() + + mBtnPlay.setOnClickListener { mListAdapter.onClickPlay() } + mBtnPause.setOnClickListener { mListAdapter.onClickPause() } + } + + private fun initList() { + val songItem = DemoDataSources.Assets.getSongs(this)[0] + + mListAdapter = MultiTrackListAdapter().apply { + mRecyclerView.adapter = this + mRecyclerView.layoutManager = LinearLayoutManager(this@MultiTrackMainActivity) + + lifecycle.addObserver(this) + + bindItems(songItem.partItems) + } + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/utils/DemoDataSources.kt b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/utils/DemoDataSources.kt new file mode 100644 index 00000000000..c6340f6fd49 --- /dev/null +++ b/demos/multi-track/src/main/java/com/github/qingmei2/exoplayer/multi_track/utils/DemoDataSources.kt @@ -0,0 +1,56 @@ +package com.github.qingmei2.exoplayer.multi_track.utils + +import android.content.Context +import android.content.res.AssetManager +import android.net.Uri +import com.github.qingmei2.exoplayer.multi_track.entity.SongItem +import com.google.android.exoplayer2.MediaItem +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader + +object DemoDataSources { + + object Assets { + + private const val SUMMER_MP3_URL = "asset:///media/summer.mp3" + private const val HALF_MOON_MP3_URL = "asset:///media/half_moon.mp3" + + private const val SUNNY_MP4_URL = "asset:///media/sunny.mp4" + + // 菊次郎的夏天.mp3 + val SUMMER_MP3: MediaItem + get() = MediaItem.fromUri(Uri.parse(SUMMER_MP3_URL)) + + // 月半小夜曲.mp3 + val HALF_MOON_MP3: MediaItem + get() = MediaItem.fromUri(Uri.parse(HALF_MOON_MP3_URL)) + + // 晴天.mp4 + val SUNNY_MP4: MediaItem + get() = MediaItem.fromUri(Uri.parse(SUNNY_MP4_URL)) + + fun getSongs(context: Context): List { + val stringBuilder = StringBuilder() + //获得assets资源管理器 + val assetManager: AssetManager = context.assets + //使用IO流读取json文件内容 + try { + val bufferedReader = BufferedReader(InputStreamReader( + assetManager.open("media/songs.json"), "utf-8")) + var line: String? + while (bufferedReader.readLine().also { line = it } != null) { + stringBuilder.append(line) + } + } catch (e: IOException) { + e.printStackTrace() + } + val json = stringBuilder.toString() + + val type = object : TypeToken>() {}.type + return Gson().fromJson(json, type) + } + } +} \ No newline at end of file diff --git a/demos/multi-track/src/main/res/layout/activity_main.xml b/demos/multi-track/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..0956ead8f90 --- /dev/null +++ b/demos/multi-track/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + +