From f8459d41bcda838d2580566b4c18fd74e6b6e6c7 Mon Sep 17 00:00:00 2001 From: Pratik Bharad Date: Mon, 3 Feb 2025 15:14:58 +0530 Subject: [PATCH 1/3] implemented looping functionality into the package. --- android/build.gradle | 27 ++-- .../FlutterRingtoneManagerPlugin.kt | 17 ++- example/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 4 +- example/lib/main.dart | 4 +- example/pubspec.lock | 84 ++++++------ .../FlutterRingtoneManagerPlugin.swift | 125 ++++++++++++------ lib/flutter_ringtone_manager.dart | 28 ++-- ...utter_ringtone_manager_method_channel.dart | 19 ++- ...r_ringtone_manager_platform_interface.dart | 8 +- lib/ios_system_sounds.dart | 4 +- pubspec.yaml | 6 +- test/flutter_ringtone_manager_test.dart | 8 +- 14 files changed, 190 insertions(+), 147 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 9b484fb..3a15e3d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,18 +2,19 @@ group 'com.example.flutter_ringtone_manager' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = "1.8.22" repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath("com.android.tools.build:gradle:8.1.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } + allprojects { repositories { google() @@ -29,15 +30,15 @@ android { namespace 'com.example.flutter_ringtone_manager' } - compileSdk 34 + compileSdk = 35 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_11 } sourceSets { @@ -46,12 +47,12 @@ android { } defaultConfig { - minSdkVersion 19 + minSdk = 21 } dependencies { - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation 'org.mockito:mockito-core:5.0.0' + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito:mockito-core:5.0.0") } testOptions { @@ -59,9 +60,9 @@ android { useJUnitPlatform() testLogging { - events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} - showStandardStreams = true + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true } } } diff --git a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt index c2bced4..501404f 100644 --- a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt +++ b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt @@ -34,11 +34,13 @@ class FlutterRingtoneManagerPlugin : FlutterPlugin, MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "playRingtone" -> { - playDefaultSoundByID(result, TYPE_RINGTONE) + val isLoop = call.argument("isLoop") ?: false + playDefaultSoundByID(result, TYPE_RINGTONE, isLoop) } "playAlarm" -> { - playDefaultSoundByID(result, TYPE_ALARM) + val isLoop = call.argument("isLoop") ?: false + playDefaultSoundByID(result, TYPE_ALARM, isLoop) } "playNotification" -> { @@ -48,8 +50,9 @@ class FlutterRingtoneManagerPlugin : FlutterPlugin, MethodCallHandler { "playAudioAsset" -> { val uri = call.argument("uri") + val isLoop = call.argument("isLoop") ?: false if (uri != null) { - playUri(Uri.parse(uri!!), result) + playUri(Uri.parse(uri!!), result, isLoop) } else { result.error("PLAY_FAILED", "assetPath can not be null", null) } @@ -72,19 +75,21 @@ class FlutterRingtoneManagerPlugin : FlutterPlugin, MethodCallHandler { channel.setMethodCallHandler(null) } - private fun playDefaultSoundByID(result: MethodChannel.Result, id: Int) { + private fun playDefaultSoundByID(result: MethodChannel.Result, id: Int, isLoop: Boolean = false) { playUri( RingtoneManager.getActualDefaultRingtoneUri(context, id), - result + result, + isLoop ) } - private fun playUri(uri: Uri, result: MethodChannel.Result) { + private fun playUri(uri: Uri, result: MethodChannel.Result, isLoop: Boolean) { try { if (ringtone != null && ringtone!!.isPlaying) { ringtone!!.stop() } ringtone = RingtoneManager.getRingtone(context, uri) + ringtone?.isLooping = isLoop ringtone?.play() } catch (e: Exception) { e.printStackTrace() diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 598d13f..165343b 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true +org.gradle.java.home=C\:\\Users\\bharadpr\\.jdks\\jbr-17.0.12 diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index e1ca574..7bb2df6 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 1d6d19b..562fda8 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -19,8 +19,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false } include ":app" diff --git a/example/lib/main.dart b/example/lib/main.dart index e9e13a7..13a15ed 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -44,7 +44,7 @@ class _MyAppState extends State { }, child: const Text("Play Ringtone")), OutlinedButton( - onPressed: () => {_flutterRingtoneManager.playAlarm()}, + onPressed: () => {_flutterRingtoneManager.playAlarm(playInLoop: true)}, child: const Text('Play Alarm')), OutlinedButton( onPressed: () { @@ -54,7 +54,7 @@ class _MyAppState extends State { ElevatedButton( onPressed: () { _flutterRingtoneManager - .playAudioAsset("audio/test.mp3"); + .playAudioAsset("audio/test.mp3", playInLoop: false); }, child: const Text("Play custom Asset")), if (Platform.isIOS) diff --git a/example/pubspec.lock b/example/pubspec.lock index 2b4d2cb..cb033ae 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" cupertino_icons: dependency: "direct main" description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" file: dependency: transitive description: @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.0.0" + version: "1.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -122,18 +122,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -162,18 +162,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -186,26 +186,26 @@ packages: dependency: transitive description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -226,18 +226,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -258,7 +258,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -287,10 +287,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" sync_http: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" vector_math: dependency: transitive description: @@ -327,34 +327,26 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" - url: "https://pub.dev" - source: hosted - version: "3.0.3" - win32: - dependency: transitive - description: - name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "3.0.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" sdks: - dart: ">=3.3.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/ios/Classes/FlutterRingtoneManagerPlugin.swift b/ios/Classes/FlutterRingtoneManagerPlugin.swift index 31fdd76..a93ad6e 100644 --- a/ios/Classes/FlutterRingtoneManagerPlugin.swift +++ b/ios/Classes/FlutterRingtoneManagerPlugin.swift @@ -22,21 +22,26 @@ public class FlutterRingtoneManagerPlugin: NSObject, FlutterPlugin { case "playAudioAsset": if let args = call.arguments as? [String: Any], let path = args["uri"] as? String { - playSound(path: path) + let isLoop = args["isLoop"] as? Bool ?? false + playSound(path: path, isLoop: isLoop) } case "playSystemSound": if let args = call.arguments as? [String: Any], let id = args["soundID"] as? Int { - playSoundByID(id: id) + let isLoop = args["isLoop"] as? Bool ?? false + playSoundByID(id: id, isLoop: isLoop) } else { result(FlutterError(code: "ID_NOT_FOUND", message: "Invalid system ID provided", details: nil)) } case "playRingtone": - playSoundByID(id: 1000) + let isLoop = (call.arguments as? [String: Any])?["isLoop"] as? Bool ?? false + playSoundByID(id: 1000, isLoop: isLoop) case "playAlarm": - playSoundByID(id: 1005) + let isLoop = (call.arguments as? [String: Any])?["isLoop"] as? Bool ?? false + playSoundByID(id: 1005, isLoop: isLoop) case "playNotification": - playSoundByID(id: 1007) + let isLoop = (call.arguments as? [String: Any])?["isLoop"] as? Bool ?? false + playSoundByID(id: 1007, isLoop: isLoop) case "stop": stop() default: @@ -44,55 +49,87 @@ public class FlutterRingtoneManagerPlugin: NSObject, FlutterPlugin { } } - func playSoundByID(id: Int) { - // Stops the player if already playing - stop() - - soundID = SystemSoundID(id) - AudioServicesPlaySystemSoundWithCompletion(soundID) { - print("play system sound finished") +// func playSoundByID(id: Int) { +// // Stops the player if already playing +// stop() +// +// soundID = SystemSoundID(id) +// AudioServicesPlaySystemSoundWithCompletion(soundID) { +// print("play system sound finished") +// } +// } + + func playSoundByID(id: Int, isLoop: Bool) { + stop() // Stop previous sound before playing a new one + + guard let soundURL = getSystemSoundPath(for: id) else { + print("Invalid system sound ID: \(id)") + return + } + + do { + audioPlayer = try AVAudioPlayer(contentsOf: soundURL) + audioPlayer?.numberOfLoops = isLoop ? -1 : 0 // Infinite loop if `isLoop` is true + audioPlayer?.prepareToPlay() + audioPlayer?.play() + } catch { + print("Error playing system sound: \(error.localizedDescription)") } } - - func playSound(path: String) { - + + func getSystemSoundPath(for id: Int) -> URL? { + let systemSounds: [Int: String] = [ + 1000: "/System/Library/Audio/UISounds/nano/Alarm_Nano.caf", + 1001: "/System/Library/Audio/UISounds/Modern/mail-sent.caf", + 1002: "/System/Library/Audio/UISounds/voicemail.caf", + 1003: "/System/Library/Audio/UISounds/Modern/sms-received1.caf", + 1004: "/System/Library/Audio/UISounds/Modern/sms-sent1.caf", + 1005: "/System/Library/Audio/UISounds/Modern/alarm.caf", + 1006: "/System/Library/Audio/UISounds/low_power.caf", + 1007: "/System/Library/Audio/UISounds/Modern/sms_alert_note.caf", + 1008: "/System/Library/Audio/UISounds/Modern/sms_alert_sent.caf", + 1009: "/System/Library/Audio/UISounds/Modern/tweet-sent.caf", + 1010: "/System/Library/Audio/UISounds/Anticipate.caf", + 1011: "/System/Library/Audio/UISounds/Bloom.caf", + 1012: "/System/Library/Audio/UISounds/Calypso.caf", + 1013: "/System/Library/Audio/UISounds/Choo_Choo.caf", + 1014: "/System/Library/Audio/UISounds/Descent.caf", + 1015: "/System/Library/Audio/UISounds/Fanfare.caf", + 1016: "/System/Library/Audio/UISounds/Ladder.caf", + 1017: "/System/Library/Audio/UISounds/Minuet.caf", + 1018: "/System/Library/Audio/UISounds/News_Flash.caf", + 1019: "/System/Library/Audio/UISounds/Noir.caf", + 1020: "/System/Library/Audio/UISounds/Sherwood_Forest.caf", + 1021: "/System/Library/Audio/UISounds/Spell.caf", + 1022: "/System/Library/Audio/UISounds/Suspense.caf", + 1023: "/System/Library/Audio/UISounds/Telegraph.caf", + 1024: "/System/Library/Audio/UISounds/Tiptoes.caf", + 1025: "/System/Library/Audio/UISounds/Typewriters.caf", + 1026: "/System/Library/Audio/UISounds/Update.caf", + 1027: "/System/Library/Audio/UISounds/ussd-alert.caf", + 1028: "/System/Library/Audio/UISounds/SIMToolkitCallDropped.caf" + ] + + if let path = systemSounds[id] { + return URL(fileURLWithPath: path) + } + return nil + } + + func playSound(path: String, isLoop: Bool = false) { let fileURL = URL(fileURLWithPath: path) do { - // Stops the player if already playing - stop() - + stop() // Stop any currently playing sound + audioPlayer = try AVAudioPlayer(contentsOf: fileURL) - guard let duration = audioPlayer?.duration else { - print("unable to detect duration of the sound") - return - } - if(duration > 30) { - audioPlayer?.prepareToPlay() - audioPlayer?.play() - } else { - playShortSound(path: path) - } + audioPlayer?.prepareToPlay() + audioPlayer?.numberOfLoops = isLoop ? -1 : 0 // Enable looping if needed + audioPlayer?.play() } catch { print("Error: Unable to play sound, error: \(error.localizedDescription)") } } - func playShortSound(path: String) { - - // Stops the player if already playing - stop() - - let fileURL = URL(fileURLWithPath: path) - let status = AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID) - if status == kAudioServicesNoError { - AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(soundID)) { - print("play short sound is finished") - } - } else { - print("Error: Unable to create sound ID, status: \(status)") - } - } - func stop() { if(audioPlayer != nil && ((audioPlayer?.isPlaying) != nil)) { audioPlayer?.stop() diff --git a/lib/flutter_ringtone_manager.dart b/lib/flutter_ringtone_manager.dart index ff9203b..306ba5a 100644 --- a/lib/flutter_ringtone_manager.dart +++ b/lib/flutter_ringtone_manager.dart @@ -5,32 +5,42 @@ import 'ios_system_sounds.dart'; class FlutterRingtoneManager { /// Plays the default ringtone of the device - FutureOr playRingtone() { - return FlutterRingtoneManagerPlatform.instance.playRingtone(); + /// there is one optional parameter also to play the ringtone in loop + FutureOr playRingtone({bool playInLoop = false}) { + return FlutterRingtoneManagerPlatform.instance.playRingtone(playInLoop: playInLoop); } /// Plays the default alarm of the device - FutureOr playAlarm() { - return FlutterRingtoneManagerPlatform.instance.playAlarm(); + /// there is one optional parameter also to play the ringtone in loop + FutureOr playAlarm({bool playInLoop = false}) { + return FlutterRingtoneManagerPlatform.instance.playAlarm(playInLoop: playInLoop); } /// Plays the default notification of the device + /// there is one optional parameter also to play the ringtone in loop FutureOr playNotification() { return FlutterRingtoneManagerPlatform.instance.playNotification(); } /// Plays the audio file which is located in assets folder of the root directory /// [assetPath] contains the [Uri] formatted [String] of the target asset - FutureOr playAudioAsset(String assetPath) { - return FlutterRingtoneManagerPlatform.instance.playAudioAsset(assetPath); + /// there is one optional parameter also to play the ringtone in loop + FutureOr playAudioAsset(String assetPath, {bool playInLoop = false}) { + return FlutterRingtoneManagerPlatform.instance.playAudioAsset( + assetPath, + playInLoop: playInLoop, + ); } /// Plays the default short sound which provides in iOS platform /// based on the param value of [SystemSoundID] /// Note: iOS SDK not provides those values as a predefined and it may change based new versions - FutureOr playIosSystemSoundByID(SystemSoundID id) { - return FlutterRingtoneManagerPlatform.instance - .playIosSystemSoundByID(id.value); + /// there is one optional parameter also to play the ringtone in loop + FutureOr playIosSystemSoundByID(SystemSoundID id, {bool playInLoop = false}) { + return FlutterRingtoneManagerPlatform.instance.playIosSystemSoundByID( + id.value, + playInLoop: playInLoop, + ); } FutureOr stop() { diff --git a/lib/flutter_ringtone_manager_method_channel.dart b/lib/flutter_ringtone_manager_method_channel.dart index b99bd48..2744d53 100644 --- a/lib/flutter_ringtone_manager_method_channel.dart +++ b/lib/flutter_ringtone_manager_method_channel.dart @@ -8,20 +8,19 @@ import 'package:path_provider/path_provider.dart'; import 'flutter_ringtone_manager_platform_interface.dart'; /// An implementation of [FlutterRingtoneManagerPlatform] that uses method channels. -class MethodChannelFlutterRingtoneManager - extends FlutterRingtoneManagerPlatform { +class MethodChannelFlutterRingtoneManager extends FlutterRingtoneManagerPlatform { /// The method channel used to interact with the native platform. @visibleForTesting final methodChannel = const MethodChannel('flutter_ringtone_manager'); @override - FutureOr playRingtone() { - methodChannel.invokeMethod('playRingtone'); + FutureOr playRingtone({bool playInLoop = false}) { + methodChannel.invokeMethod('playRingtone', {"isLoop": playInLoop}); } @override - FutureOr playAlarm() { - methodChannel.invokeMethod('playAlarm'); + FutureOr playAlarm({bool playInLoop = false}) { + methodChannel.invokeMethod('playAlarm', {"isLoop": playInLoop}); } @override @@ -30,17 +29,17 @@ class MethodChannelFlutterRingtoneManager } @override - Future> playAudioAsset(String assetPath) async { + Future> playAudioAsset(String assetPath, {bool playInLoop = false}) async { final bytes = await rootBundle.load("assets/$assetPath"); final file = File('${(await getTemporaryDirectory()).path}/$assetPath'); await file.create(recursive: true); await file.writeAsBytes(bytes.buffer.asUint8List()); - methodChannel.invokeMethod('playAudioAsset', {'uri': file.uri.path}); + methodChannel.invokeMethod('playAudioAsset', {'uri': file.uri.path, "isLoop": playInLoop}); } @override - FutureOr playIosSystemSoundByID(int id) { - methodChannel.invokeMethod('playSystemSound', {'soundID': id}); + FutureOr playIosSystemSoundByID(int id, {bool playInLoop = false}) { + methodChannel.invokeMethod('playSystemSound', {'soundID': id, "isLoop": playInLoop}); } @override diff --git a/lib/flutter_ringtone_manager_platform_interface.dart b/lib/flutter_ringtone_manager_platform_interface.dart index 25b377b..26b237d 100644 --- a/lib/flutter_ringtone_manager_platform_interface.dart +++ b/lib/flutter_ringtone_manager_platform_interface.dart @@ -26,11 +26,11 @@ abstract class FlutterRingtoneManagerPlatform extends PlatformInterface { _instance = instance; } - FutureOr playRingtone() { + FutureOr playRingtone({bool playInLoop = false}) { throw UnimplementedError('playRingtone() has not been implemented'); } - FutureOr playAlarm() { + FutureOr playAlarm({bool playInLoop = false}) { throw UnimplementedError('playAlarm() has not been implemented'); } @@ -38,11 +38,11 @@ abstract class FlutterRingtoneManagerPlatform extends PlatformInterface { throw UnimplementedError('playNotification() has not been implemented'); } - FutureOr playAudioAsset(String assetPath) { + FutureOr playAudioAsset(String assetPath, {bool playInLoop = false}) { throw UnimplementedError('playAudioAsset() has not been implemented'); } - FutureOr playIosSystemSoundByID(int id) { + FutureOr playIosSystemSoundByID(int id, {bool playInLoop = false}) { throw UnimplementedError( 'playIosSystemSoundByID is iOS platform specific method which is not implemented'); } diff --git a/lib/ios_system_sounds.dart b/lib/ios_system_sounds.dart index 48d08a2..44b7ab4 100644 --- a/lib/ios_system_sounds.dart +++ b/lib/ios_system_sounds.dart @@ -92,8 +92,6 @@ extension SystemSoundIDExtension on SystemSoundID { return 1027; case SystemSoundID.simToolkitCallDropped: return 1028; - default: - throw Exception('Unknown SystemSoundID'); - } + } } } diff --git a/pubspec.yaml b/pubspec.yaml index 771cdf0..1d4c37b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,13 +13,13 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - path_provider: ^2.1.3 - plugin_platform_interface: ^2.0.2 + path_provider: ^2.1.5 + plugin_platform_interface: ^2.1.8 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 flutter: plugin: diff --git a/test/flutter_ringtone_manager_test.dart b/test/flutter_ringtone_manager_test.dart index dc96055..8550de8 100644 --- a/test/flutter_ringtone_manager_test.dart +++ b/test/flutter_ringtone_manager_test.dart @@ -9,19 +9,19 @@ class MockFlutterRingtoneManagerPlatform with MockPlatformInterfaceMixin implements FlutterRingtoneManagerPlatform { @override - FutureOr playAlarm() {} + FutureOr playAlarm({bool playInLoop = false}) {} @override - FutureOr playAudioAsset(String assetPath) {} + FutureOr playAudioAsset(String assetPath, {bool playInLoop = false}) {} @override FutureOr playNotification() {} @override - FutureOr playRingtone() {} + FutureOr playRingtone({bool playInLoop = false}) {} @override - FutureOr playIosSystemSoundByID(int id) {} + FutureOr playIosSystemSoundByID(int id, {bool playInLoop = false}) {} @override FutureOr stop() {} From 3a20974921efd5fb9da9543fa367594b9b32652c Mon Sep 17 00:00:00 2001 From: Pratik Bharad Date: Mon, 3 Feb 2025 18:37:46 +0530 Subject: [PATCH 2/3] implemented the get URI By Id to get the Url. --- .../FlutterRingtoneManagerPlugin.kt | 20 +++++++++++++++++++ .../FlutterRingtoneManagerPlugin.swift | 11 ++++++++++ lib/flutter_ringtone_manager.dart | 10 ++++++++++ ...utter_ringtone_manager_method_channel.dart | 5 +++++ ...r_ringtone_manager_platform_interface.dart | 4 ++++ test/flutter_ringtone_manager_test.dart | 15 ++++++++------ 6 files changed, 59 insertions(+), 6 deletions(-) diff --git a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt index 501404f..d662154 100644 --- a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt +++ b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt @@ -64,6 +64,26 @@ class FlutterRingtoneManagerPlugin : FlutterPlugin, MethodCallHandler { ringtone?.stop() } } + "getUriOfSystemSoundByID" -> { + val soundId = call.argument("soundID") ?: 1007 + when (soundId) { + 1007 -> { + val smsSentToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + result.success(smsSentToneUri.toString()) + } + 1005 -> { + val alarmToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) + result.success(alarmToneUri.toString()) + } + 1000 -> { + val alarmToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) + result.success(alarmToneUri.toString()) + } + else -> { + result.error("URI_GET_FAILED", "Invalid system sound ID for Android", null) + } + } + } else -> { result.notImplemented() diff --git a/ios/Classes/FlutterRingtoneManagerPlugin.swift b/ios/Classes/FlutterRingtoneManagerPlugin.swift index a93ad6e..904298a 100644 --- a/ios/Classes/FlutterRingtoneManagerPlugin.swift +++ b/ios/Classes/FlutterRingtoneManagerPlugin.swift @@ -44,6 +44,17 @@ public class FlutterRingtoneManagerPlugin: NSObject, FlutterPlugin { playSoundByID(id: 1007, isLoop: isLoop) case "stop": stop() + case "getUriOfSystemSoundByID": + if let args = call.arguments as? [String: Any], + let id = args["soundID"] as? Int { + if let soundURL = getSystemSoundPath(for: id) { + result(soundURL.absoluteString) // Return the URI as a string + } else { + result(FlutterError(code: "ID_NOT_FOUND", message: "Invalid system sound ID provided", details: nil)) + } + } else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid soundID", details: nil)) + } default: result(FlutterMethodNotImplemented) } diff --git a/lib/flutter_ringtone_manager.dart b/lib/flutter_ringtone_manager.dart index 306ba5a..ac976e1 100644 --- a/lib/flutter_ringtone_manager.dart +++ b/lib/flutter_ringtone_manager.dart @@ -46,4 +46,14 @@ class FlutterRingtoneManager { FutureOr stop() { return FlutterRingtoneManagerPlatform.instance.stop(); } + + /// gets the URI of the default sound which provides in iOS platform based on ids + /// & for Android platform we are using three of the [SystemSoundID] to get the URIs + /// 1. [SystemSoundID.alarm] will work in both android & ios to get the alarm sound URI + /// 2. [SystemSoundID.newMail] will work in both android & ios to get the Ringtone sound URI + /// 3. [SystemSoundID.smsReceived] will work in both android & ios to get the Notification sound URI + /// Note: iOS SDK not provides those values as a predefined and it may change based new versions + FutureOr getUriOfSystemSoundByID(SystemSoundID id) { + return FlutterRingtoneManagerPlatform.instance.getUriOfSystemSoundByID(id.value); + } } diff --git a/lib/flutter_ringtone_manager_method_channel.dart b/lib/flutter_ringtone_manager_method_channel.dart index 2744d53..3ef0ee9 100644 --- a/lib/flutter_ringtone_manager_method_channel.dart +++ b/lib/flutter_ringtone_manager_method_channel.dart @@ -46,4 +46,9 @@ class MethodChannelFlutterRingtoneManager extends FlutterRingtoneManagerPlatform FutureOr stop() { methodChannel.invokeMethod('stop'); } + + @override + FutureOr getUriOfSystemSoundByID(int id) async { + return await methodChannel.invokeMethod('getUriOfSystemSoundByID', {'soundID': id}); + } } diff --git a/lib/flutter_ringtone_manager_platform_interface.dart b/lib/flutter_ringtone_manager_platform_interface.dart index 26b237d..9fbe37c 100644 --- a/lib/flutter_ringtone_manager_platform_interface.dart +++ b/lib/flutter_ringtone_manager_platform_interface.dart @@ -50,4 +50,8 @@ abstract class FlutterRingtoneManagerPlatform extends PlatformInterface { FutureOr stop() { throw UnimplementedError('stop() has not been implemented'); } + + FutureOr getUriOfSystemSoundByID(int id) { + throw UnimplementedError('stop() has not been implemented'); + } } diff --git a/test/flutter_ringtone_manager_test.dart b/test/flutter_ringtone_manager_test.dart index 8550de8..2f9322e 100644 --- a/test/flutter_ringtone_manager_test.dart +++ b/test/flutter_ringtone_manager_test.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_ringtone_manager/flutter_ringtone_manager_platform_interface.dart'; import 'package:flutter_ringtone_manager/flutter_ringtone_manager_method_channel.dart'; +import 'package:flutter_ringtone_manager/flutter_ringtone_manager_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; class MockFlutterRingtoneManagerPlatform @@ -25,14 +25,17 @@ class MockFlutterRingtoneManagerPlatform @override FutureOr stop() {} + + @override + FutureOr getUriOfSystemSoundByID(int id) { + return ""; + } } void main() { - final FlutterRingtoneManagerPlatform initialPlatform = - FlutterRingtoneManagerPlatform.instance; + final FlutterRingtoneManagerPlatform initialPlatform = FlutterRingtoneManagerPlatform.instance; test('$MethodChannelFlutterRingtoneManager is the default instance', () { - expect( - initialPlatform, isInstanceOf()); + expect(initialPlatform, isInstanceOf()); }); } From f1bee861f25d077569b355e501a38e07f26590c0 Mon Sep 17 00:00:00 2001 From: Pratik Bharad Date: Tue, 4 Feb 2025 12:37:25 +0530 Subject: [PATCH 3/3] implemented the get URI By Id to get the Url. --- .../FlutterRingtoneManagerPlugin.kt | 6 +++--- example/lib/main.dart | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt index d662154..0a1a1b6 100644 --- a/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt +++ b/android/src/main/kotlin/com/example/flutter_ringtone_manager/FlutterRingtoneManagerPlugin.kt @@ -68,15 +68,15 @@ class FlutterRingtoneManagerPlugin : FlutterPlugin, MethodCallHandler { val soundId = call.argument("soundID") ?: 1007 when (soundId) { 1007 -> { - val smsSentToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + val smsSentToneUri: Uri = RingtoneManager.getActualDefaultRingtoneUri(context, TYPE_NOTIFICATION) result.success(smsSentToneUri.toString()) } 1005 -> { - val alarmToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) + val alarmToneUri: Uri = RingtoneManager.getActualDefaultRingtoneUri(context, TYPE_ALARM) result.success(alarmToneUri.toString()) } 1000 -> { - val alarmToneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) + val alarmToneUri: Uri = RingtoneManager.getActualDefaultRingtoneUri(context, TYPE_RINGTONE) result.success(alarmToneUri.toString()) } else -> { diff --git a/example/lib/main.dart b/example/lib/main.dart index 13a15ed..f8ad10d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,6 +18,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final _flutterRingtoneManager = FlutterRingtoneManager(); + String? alarmUri = "get alarm Uri"; @override void initState() { @@ -43,6 +44,12 @@ class _MyAppState extends State { _flutterRingtoneManager.playRingtone(); }, child: const Text("Play Ringtone")), + OutlinedButton( + onPressed: () async { + alarmUri = await _flutterRingtoneManager.getUriOfSystemSoundByID(SystemSoundID.alarm); + setState(() {}); + }, + child: Text(alarmUri!)), OutlinedButton( onPressed: () => {_flutterRingtoneManager.playAlarm(playInLoop: true)}, child: const Text('Play Alarm')),