Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 31
buildToolsVersion "33.0.0"

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand Down
17 changes: 17 additions & 0 deletions android/src/main/aidl/tech/soit/quiet/ISessionDataProvider.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ISessionDataProvider.aidl
package tech.soit.quiet;

import tech.soit.quiet.player.MusicMetadata;
import tech.soit.quiet.MusicResult;

parcelable ArtworkData;

interface ISessionDataProvider {

ArtworkData loadArtwork(in MusicMetadata metadata);

String getPlayerUrl(String id, String fallbackUrl);

}


30 changes: 30 additions & 0 deletions android/src/main/kotlin/tech/soit/quiet/ArtworkData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package tech.soit.quiet

import android.os.Parcel
import android.os.Parcelable

class ArtworkData(
val color: Int?,
val image: ByteArray
) : Parcelable {

constructor(source: Parcel) : this(
source.readValue(Int::class.java.classLoader) as Int?,
source.createByteArray()!!
)

override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeValue(color)
writeByteArray(image)
}

companion object {
@JvmField
val CREATOR: Parcelable.Creator<ArtworkData> = object : Parcelable.Creator<ArtworkData> {
override fun createFromParcel(source: Parcel): ArtworkData = ArtworkData(source)
override fun newArray(size: Int): Array<ArtworkData?> = arrayOfNulls(size)
}
}
}
190 changes: 188 additions & 2 deletions android/src/main/kotlin/tech/soit/quiet/MusicPlayerUiPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.os.IBinder
import android.os.SystemClock
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSource
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
Expand All @@ -23,11 +30,11 @@ private const val UI_PLUGIN_NAME = "tech.soit.quiet/player.ui"

class MusicPlayerUiPlugin : FlutterPlugin {

private var playerUiChannel: MusicPlayerUiChannel? = null
private var playerUiChannel: AudioPlayerChannel? = null

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val channel = MethodChannel(binding.binaryMessenger, UI_PLUGIN_NAME)
playerUiChannel = MusicPlayerUiChannel(channel, binding.applicationContext)
playerUiChannel = AudioPlayerChannel(channel, binding.applicationContext)
channel.setMethodCallHandler(playerUiChannel)
}

Expand All @@ -38,6 +45,185 @@ class MusicPlayerUiPlugin : FlutterPlugin {
}


class AudioPlayerChannel(
private val channel: MethodChannel,
private val context: Context
) : MethodChannel.MethodCallHandler {

companion object {
var lastCreatedPlayerId = 0L
}

init {
channel.setMethodCallHandler(this)
}

private val players = mutableMapOf<Long, AudioPlayer>()

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"createPlayer" -> {
val uri = Uri.parse(call.argument("uri"))
val playerId = ++lastCreatedPlayerId
players[playerId] = AudioPlayer(playerId, uri, context, channel)
result.success(playerId)
}
"prepare" -> {
val playerId = call.argument<Long>("id")!!
val playWhenReady = call.argument<Boolean>("playWhenReady")!!
players[playerId]?.prepare(playWhenReady)
result.success(null)
}
"setPlayWhenReady" -> {
val playerId = call.argument<Long>("id")!!
val playWhenReady = call.argument<Boolean>("playWhenReady")!!
players[playerId]?.setPlayWhenReady(playWhenReady)
result.success(null)
}

"setVolume" -> {
val playerId = call.argument<Long>("id")!!
val volume = call.argument<Double>("volume")!!
players[playerId]?.setVolume(volume.toFloat())
result.success(null)
}
"seekTo" -> {
val playerId = call.argument<Long>("id")!!
val position = call.argument<Long>("position")!!
players[playerId]?.seekTo(position)
result.success(null)
}
"getBufferedPosition" -> {
val playerId = call.argument<Long>("id")!!
val position = players[playerId]?.getBufferedPosition()
result.success(position)
}
"dispose" -> {
val playerId = call.argument<Long>("id")!!
players[playerId]?.release()
players.remove(playerId)
result.success(null)
}
else -> {
result.notImplemented()
}

}
}

fun destroy() {
players.values.forEach { it.release() }
players.clear()
}

}

class AudioPlayer(
private val playerId: Long,
uri: Uri,
context: Context,
private val channel: MethodChannel
) : Player.Listener {

companion object {
private val audioAttribute = AudioAttributes.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.setUsage(C.USAGE_MEDIA)
.build()
}

// Wrap a SimpleExoPlayer with a decorator to handle audio focus for us.
private val player: ExoPlayer = ExoPlayer.Builder(context)
.setAudioAttributes(audioAttribute, true)
.build()


init {
val factory: DataSource.Factory = DefaultDataSource.Factory(context)
player.setMediaSource(
ProgressiveMediaSource.Factory(factory)
.createMediaSource(MediaItem.fromUri(uri))
)
player.addListener(this)
}

fun setPlayWhenReady(playWhenReady: Boolean) {
player.playWhenReady = playWhenReady
}

fun prepare(playWhenReady: Boolean) {
player.prepare()
player.playWhenReady = playWhenReady
}

fun setVolume(volume: Float) {
player.volume = volume
}

fun seekTo(position: Long) {
player.seekTo(position)
}

fun release() {
player.release()
}

fun getBufferedPosition(): Long {
return player.bufferedPosition
}

override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
notifyPositionChanged()
channel.invokeMethod(
"onPlayWhenReadyChanged", mapOf(
"id" to playerId,
"playWhenReady" to playWhenReady,
"reason" to reason,
)
)
}

private fun notifyPositionChanged() {
channel.invokeMethod(
"onPositionChanged", mapOf(
"id" to playerId,
"position" to player.currentPosition,
"duration" to player.duration,
"updateTime" to SystemClock.elapsedRealtime(),
)
)
}

override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
notifyPositionChanged()
}

override fun onPlaybackStateChanged(playbackState: Int) {
channel.invokeMethod(
"onPlaybackStateChanged", mapOf(
"id" to playerId,
"playbackState" to playbackState,
"duration" to player.duration,
)
)
}

override fun onPlayerError(error: PlaybackException) {
channel.invokeMethod(
"onPlayerError", mapOf(
"id" to playerId,
"errorCode" to error.errorCode,
"message" to error.message,
)
)
}

}

private class MusicPlayerUiChannel(
channel: MethodChannel,
context: Context
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ android {
applicationId "tech.soit.example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
6 changes: 0 additions & 6 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ PODS:
- Flutter
- SwiftAudioEx (~> 0.15.3)
- SwiftAudioEx (0.15.3)
- system_clock (0.0.1):
- Flutter

DEPENDENCIES:
- Flutter (from `Flutter`)
- music_player (from `.symlinks/plugins/music_player/ios`)
- system_clock (from `.symlinks/plugins/system_clock/ios`)

SPEC REPOS:
trunk:
Expand All @@ -21,14 +18,11 @@ EXTERNAL SOURCES:
:path: Flutter
music_player:
:path: ".symlinks/plugins/music_player/ios"
system_clock:
:path: ".symlinks/plugins/system_clock/ios"

SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
music_player: 2cb8e84bc904013b04392560f0b94d1e96e71e14
SwiftAudioEx: 83eabba2940924fc1c0d5cb0896049921365229c
system_clock: 3efb51d18e565092e2a97bc605c6255f328bd13a

PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048

Expand Down
4 changes: 3 additions & 1 deletion example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -221,6 +221,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand All @@ -235,6 +236,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down
2 changes: 2 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
31 changes: 1 addition & 30 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:music_player_example/page_play_queue.dart';
import 'package:music_player_example/player/background.dart';
import 'package:overlay_support/overlay_support.dart';

import 'player/music_metadata.dart';
import 'player/player.dart';
import 'player/player_bottom_controller.dart';

Expand Down Expand Up @@ -55,34 +54,6 @@ void main() {
runApp(ExampleApp());
}

@pragma("vm:entry-point")
void playerBackgroundService() async {
debugPrint("start playerBackgroundService");
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
await Future.delayed(const Duration(milliseconds: 100));
runBackgroundService(
config: Config(
pauseWhenTaskRemoved: false,
),
playUriInterceptor: (mediaId, fallbackUrl) async {
debugPrint("get media play uri : $mediaId , $fallbackUrl");
if (mediaId == 'rise') return "asset:///tracks/rise.mp3";
return fallbackUrl;
},
imageLoadInterceptor: (metadata) async {
debugPrint("load image for ${metadata.mediaId} , ${metadata.title}");
if (metadata.mediaId == "bamboo") {
final data = await rootBundle.load("images/bamboo.jpg");
return Uint8List.view(data.buffer);
}
return null;
},
playQueueInterceptor: ExamplePlayQueueInterceptor(),
);
}

class ExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Expand Down
Loading