Flutter Audio Mix Nasıl Yapılır?

Yasin Ege
Naylalabs

--

Selamlar, geçenlerde bir projede ihtiyacım doğrultusunda Audio Mix ( ses birleştirme) kullanmam gerekti ve bu seslerin telefon arka plana atılsa dahi çalmaya devam etmesine ihtiyacım vardı, araştırmalarımı sürdürürken bu konuda açıklayıcı bir kaynak olmadığını farkettim ve bir makale yazarak, örnek bir projeyle bu sorunu kendim nasıl çözdüm bundan bahsetmek istedim. Dilerseniz başlayalım…

GİRİŞ

Bu makalede amaç birden fazla müzik dosyasını aynı anda oynatmak, durdurmak ve devam ettirmektir. Bu işlemler arka planda gerçekleşeceği için uygulamayı arka plana atsanız bile ses dosyalarımız çalmaya devam edecektir. Kullandığımız audio_service paketi tek bir player üzerinden işlemleri gerçekleştirmektedir ben ise bu sınıfı özelleştirerek servis sınıfımız içerisinde birden fazla audio playeri yöneterek sorunumu çözdüm.Dilerseniz kurulum adımına geçelim :)

KURULUM

Öncelikle ses dosyalarını oynatmamızı sağlayan ve birçok özellik barındıran just_audio paketini ve arka planda seslerin oynamaya devam etmesi için audio_service paketini pubspec.yaml dosyamıza eklememiz ve daha sonra “flutter pub get” yaparak projeye import etmeliyiz.

just_audio: ^0.9.20
audio_service: ^0.18.4

Android ve IOS için native dosyalarda bazı ekleme/düzenlemeler yapmalıyız. Sorun yaşarsanız bu linki inceleyerek kurulum aşamasını tamamlayabilirsiniz.

IOS kurulum:

Info.plist dosyasına alttaki kodu eklememiz yeterli.

<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>

Android kurulum:

Manifest dosyamız’da audio_service paketini kullanmak için gerekli izinleri ekleyelim.

<manifest xmlns:tools="http://schemas.android.com/tools" ...>
<!-- Bu iki izni eklemeliyiz-->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<application ...>

...

<!-- Burası önemli, activity ismi kullandığımız paketi referans etmeli -->
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" ...>
...
</activity>

<!-- ADD THIS "SERVICE" element -->
<service android:name="com.ryanheise.audioservice.AudioService"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>

<!-- ADD THIS "RECEIVER" element -->
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>

Kullanacağımız Sınıflar

AudioHandler : Ses dosyalarının arka planda’ da oynamaya devam etmesine yarayan ve oynatılan ses dosyası için bildirim göstermemize yarayan bir servis gibi düşünebiliriz, siz bu classı daha da özelleştirerek bildirim çubuğunda müzik bilgilerini gösterebilirsiniz(spotify gibi), kütüphane içeriğini incelemenizi tavsiye ederim.

AudioPlayer: just_audio paketinden import edilen bu class herhangi bir kaynaktan (asset/dosya/URL/akış) herhangi bir sesi oynatmamızı ve kontrol etmemizi sağlar.

KODA GEÇELİM

Öncelikle arka planda sesleri oynatmaya devam etmek için AudioHandler sınıfını init(kurmamız) etmemiz gerekiyor, ben dependency injection ile singleton ve daha temiz bir şekilde kullanmanızı tavsiye ediyorum fakat burada amacımız audio mix ‘i anlatmak olduğu için bu teorik kısımları es geçiyorum ve main() içerisinde Audio servisimizi init ediyorum.

// You might want to provide this using dependency injection rather //than a global variable.
late AudioHandler audioHandler;
Future<void> main() async {
audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.naylalabs.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(const MyApp());
}

Evet artık servisimizi init ettik farkettiyseniz buildera AudioPlayerHandler() classını verdik, bu classı biz kendimiz oluşturacağız ve bu class BaseAudioHandler classını extend ederek audio servisini özelleştirerek kullanmamızı sağlayacak.

class AudioPlayerHandler extends BaseAudioHandler {

/// Initialise our audio handler.
AudioPlayerHandler() {
_init();
}

//TODO WILL CHANGE FOR SHOW FOREGROUND NOTIFICATION ON NEXT VERSION
Future<void> _init() async {
}

DEVAM…

Daha sonra MixAudioPlayer adlı classımızı eklememiz gerekiyor, bu classın kendi id ‘ si olacak. AudioPlayer sınıfını extend ederek AudioPlayer sınıfının fonksiyonlarını kullanabileceğiz. Bu sayede AudioPlayerHandler servisimiz içerisinde player’ları göndereceğimiz id’lere göre karşılaştırma yaparak oynatabilecek veya durdurabileceğiz.

class MixAudioPlayer extends AudioPlayer{
final String id;

MixAudioPlayer(this.id);
}

Daha sonra AudioPlayerHandler classımıza ses dosyalarını yönetmek için listemizi ekliyoruz , kullanıcı play veya pause tuşuna bastığında gerekli düzenlemelerle bu listeden ses dosyalarımızı yöneteceğiz.

final List<MixAudioPlayer> _playerList = [];

Eklediğimiz AudioPlayerHandler sınıfı içerisinde kütüphanenin bize sağladığı customAction fonksiyonunu kullanacağız . audioHandler.customAction fonksiyonu ile main de init ettiğimiz audioHandler objemizi kullanarak servisimiz ile iletişime geçerek ses dosyalarımızı oynatabilecek veya durdurabileceğiz.

mixMusic” fonksiyonu göndereceğimiz “url” ve “id” bilgisini kullanarak belirtilen ses dosyasını oynatmamıza olanak sağlar.

pause” fonksiyonu gelen id bilgisini AudioHandler classına eklediğimiz _playerList içerisindeki elemanlarla kıyaslayarak mevcut id’de aktif player varsa onu dispose eder ve listeden çıkartır.

pauseAll” aksiyonu çalan tüm ses dosyalarını pause eder.

@override
Future customAction(String name, [Map<String, dynamic>? extras]) async {
switch (name) {
case 'mixMusic':
try {
final player = MixAudioPlayer(extras!["id"]);
await player.setUrl(extras["url"]);
_playerList.add(player);
player.play();
} catch (e) {
print("Error on mix music: $e");
// onStop();
}
break;
case 'pause':
try {
var id = extras!["id"];
for( int i =0; i< _playerList.length ; i++){
if(_playerList[i].id == id){
_playerList[i].dispose();
_playerList.removeAt(i);
}
}

}
catch (e) {
print("Player custom action pause Error: $e");
}
break;

case 'pauseAll':
try {
pause();
} catch (e) {
print("Player custom action pause Error: $e");
}
break;
}
return super.customAction(name, extras);
}

Uygulamamız içerisinde play butonuna bastığımızda ses oynatılıyor ise “pause” aksi durumda “mixMusic” aksiyonunu göndereceğiz, parametre olarak id ve url göndereceğiz.

play(SoundModel model) async {
if (model.isPlaying) {
model.isPlaying = false;
audioHandler.customAction("pause", {"id": model.id});
} else {
model.isPlaying = true;
audioHandler
.customAction("mixMusic", {"url": model.media!.url, "id": model.id});

}

Tebrikler artık birden fazla ses dosyasını arka planda ve aynı anda oynatabiliyoruz. Ufak eklentilerle uygulamamızı geliştirmeye devam edelim.

Şimdi eklediğimiz AudioPlayerHandler sınıfında override edilen play() ve pause() fonksiyonlarını kendimiz değiştirerek özelleştireceğiz bu sayede uygulamanın herhangi bir yerinde tüm ses dosyalarını play / pause etmek istediğinizde audioHandler.play() / audioHandler.pause() fonksiyonlarını çağırarak kullanabileceğiz.

//_playerList'te bulunan tüm ses dosyalarını oynatır.
@override
Future<void> play() async {
for (var element in _playerList) {
await element.play();
}
}
//_playerList'te bulunan tüm ses dosyalarını duraklatır.
@override
Future<void> pause() async {
for (var element in _playerList) {
await element.pause();
}
}

AudioPlayerHandler classımızın son hali:


class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {

final _player = AudioPlayer();
final List<MixAudioPlayer> _playerList = [];

/// Initialise our audio handler.
AudioPlayerHandler() {
_init();
}

Future<void> _init() async {
// You can customize it
}


@override
Future customAction(String name, [Map<String, dynamic>? extras]) async {
switch (name) {
case 'mixMusic':
try {
final player = MixAudioPlayer(extras!["id"]);
await player.setUrl(extras["url"]);
_playerList.add(player);
player.play();
} catch (e) {
print("Error on mix music: $e");
// onStop();
}
break;
case 'pause':
try {
var id = extras!["id"];
for( int i =0; i< _playerList.length ; i++){
if(_playerList[i].id == id){
_playerList[i].dispose();
_playerList.removeAt(i);
}
}

}
catch (e) {
print("Player custom action pause Error: $e");
}
break;

case 'pauseAll':
try {
pause();
} catch (e) {
print("Player custom action pause Error: $e");
}
break;
}
return super.customAction(name, extras);
}

@override
Future<void> play() async {
for (var element in _playerList) {
await element.play();
}
}

@override
Future<void> pause() async {
for (var element in _playerList) {
await element.pause();
}
}


@override
Future<void> stop() => _player.stop();

}

Uygulamamızın son hali

VE SON

Bugün sizlere Flutter’da arka planda da çalışabilen audio mix nasıl yapılır kabaca anlatmaya çalıştım ve kendi çözüm yöntemimden bahsettim, uygulama hakkında daha detaylı bilgi almak ve katkıda bulunmak için ekte sunulan projeye göz atabilirsiniz. Ayrıca eklediğimiz kütüphaneleri inceleyerek kendiniz bu yapıyı daha da özelleştirebilirsiniz. Umarım açıklayıcı bir anlatım olmuştur teşekkürler.

--

--

Yasin Ege
Naylalabs

Android Developer at Bilyoner — Flutter & Android