From 0539466850aaa49a0bde9448939c6c3d536dd6e2 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sun, 20 Aug 2023 05:24:40 +0200 Subject: [PATCH] refactor: Lint Kotlin, C and C++ code (#1610) Test Linting Github Action Super Linter, to uniform source code: https://github.com/marketplace/actions/super-linter --- .clang-format | 4 + .editorconfig | 9 + .github/workflows/test.yml | 9 + .../luan/audioplayers/example/MainActivity.kt | 3 +- .../example/linux/my_application.cc | 21 +- .../example/linux/my_application.h | 5 +- .../example/windows/runner/flutter_window.cpp | 3 +- .../example/windows/runner/flutter_window.h | 4 +- .../example/windows/runner/main.cpp | 9 +- .../example/windows/runner/resource.h | 10 +- .../example/windows/runner/utils.cpp | 11 +- .../audioplayers_android/android/build.gradle | 2 +- ...AudioContext.kt => AudioContextAndroid.kt} | 22 +- .../luan/audioplayers/AudioplayersPlugin.kt | 6 +- .../xyz/luan/audioplayers/ByteDataSource.kt | 3 +- .../xyz/luan/audioplayers/PlayerMode.kt | 2 +- .../xyz/luan/audioplayers/ReleaseMode.kt | 2 +- .../luan/audioplayers/player/FocusManager.kt | 4 +- .../audioplayers/player/SoundPoolPlayer.kt | 2 +- .../luan/audioplayers/player/WrappedPlayer.kt | 13 +- .../luan/audioplayers/source/BytesSource.kt | 6 +- .../xyz/luan/audioplayers/source/Source.kt | 2 +- .../xyz/luan/audioplayers/source/UrlSource.kt | 6 +- .../luan/audioplayers/ToConstantCaseTest.kt | 2 +- .../ios/Classes/AudioplayersDarwinPlugin.h | 2 +- .../macos/Classes/AudioplayersDarwinPlugin.h | 2 +- .../audioplayers_linux/linux/audio_player.cc | 736 +++++++++--------- .../audioplayers_linux/linux/audio_player.h | 112 +-- .../linux/audioplayers_linux_plugin.cc | 475 +++++------ .../audioplayers_linux_plugin.h | 4 +- .../windows/MediaEngineExtension.cpp | 135 ++-- .../windows/MediaEngineExtension.h | 60 +- .../windows/MediaEngineWrapper.cpp | 606 +++++++------- .../windows/MediaEngineWrapper.h | 147 ++-- .../windows/MediaFoundationHelpers.h | 227 +++--- .../windows/audio_player.cpp | 332 ++++---- .../windows/audio_player.h | 81 +- .../windows/audioplayers_helpers.h | 18 +- .../windows/audioplayers_windows_plugin.cpp | 392 +++++----- .../windows/event_stream_handler.h | 75 +- 40 files changed, 1798 insertions(+), 1766 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig rename packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/{AudioContext.kt => AudioContextAndroid.kt} (76%) diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..6165b7078 --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium +Standard: c++17 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..ff89a2799 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[{*.kt,*.kts}] +max_line_length = 120 +insert_final_newline = true +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_size = 4 +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_allow_trailing_comma = true +ij_kotlin_name_count_to_use_star_import = 999 +ij_kotlin_name_count_to_use_star_import_for_members = 999 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a1e318c6..3cd4101e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,6 +100,15 @@ jobs: - run: melos run analyze -- ${{ inputs.fatal_warnings && '--fatal-infos' || '--no-fatal-warnings --no-fatal-infos' }} - run: melos run test + - name: Lint Code Base + uses: super-linter/super-linter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: main + VALIDATE_KOTLIN_ANDROID: true + VALIDATE_CLANG_FORMAT: true + web: runs-on: ubuntu-latest timeout-minutes: 30 diff --git a/packages/audioplayers/example/android/app/src/main/kotlin/xyz/luan/audioplayers/example/MainActivity.kt b/packages/audioplayers/example/android/app/src/main/kotlin/xyz/luan/audioplayers/example/MainActivity.kt index f36e91b7d..9d43d9f3e 100644 --- a/packages/audioplayers/example/android/app/src/main/kotlin/xyz/luan/audioplayers/example/MainActivity.kt +++ b/packages/audioplayers/example/android/app/src/main/kotlin/xyz/luan/audioplayers/example/MainActivity.kt @@ -2,5 +2,4 @@ package xyz.luan.audioplayers.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/packages/audioplayers/example/linux/my_application.cc b/packages/audioplayers/example/linux/my_application.cc index 0f374e4e2..3ffde7d7c 100644 --- a/packages/audioplayers/example/linux/my_application.cc +++ b/packages/audioplayers/example/linux/my_application.cc @@ -51,7 +51,8 @@ static void my_application_activate(GApplication* application) { gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); @@ -63,16 +64,18 @@ static void my_application_activate(GApplication* application) { } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); @@ -90,7 +93,8 @@ static void my_application_dispose(GObject* object) { static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } @@ -98,7 +102,6 @@ static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, - nullptr)); + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); } diff --git a/packages/audioplayers/example/linux/my_application.h b/packages/audioplayers/example/linux/my_application.h index 72271d5e4..db16367a7 100644 --- a/packages/audioplayers/example/linux/my_application.h +++ b/packages/audioplayers/example/linux/my_application.h @@ -3,7 +3,10 @@ #include -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, GtkApplication) /** diff --git a/packages/audioplayers/example/windows/runner/flutter_window.cpp b/packages/audioplayers/example/windows/runner/flutter_window.cpp index 3a11b51d9..3459ea6b2 100644 --- a/packages/audioplayers/example/windows/runner/flutter_window.cpp +++ b/packages/audioplayers/example/windows/runner/flutter_window.cpp @@ -38,7 +38,8 @@ void FlutterWindow::OnDestroy() { } LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, +FlutterWindow::MessageHandler(HWND hwnd, + UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. diff --git a/packages/audioplayers/example/windows/runner/flutter_window.h b/packages/audioplayers/example/windows/runner/flutter_window.h index 28c23839b..9d361e6b1 100644 --- a/packages/audioplayers/example/windows/runner/flutter_window.h +++ b/packages/audioplayers/example/windows/runner/flutter_window.h @@ -19,7 +19,9 @@ class FlutterWindow : public Win32Window { // Win32Window: bool OnCreate() override; void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, LPARAM const lparam) noexcept override; private: diff --git a/packages/audioplayers/example/windows/runner/main.cpp b/packages/audioplayers/example/windows/runner/main.cpp index 85308b6c3..a8b499bea 100644 --- a/packages/audioplayers/example/windows/runner/main.cpp +++ b/packages/audioplayers/example/windows/runner/main.cpp @@ -5,8 +5,10 @@ #include "flutter_window.h" #include "utils.h" -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { +int APIENTRY wWinMain(_In_ HINSTANCE instance, + _In_opt_ HINSTANCE prev, + _In_ wchar_t* command_line, + _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { @@ -19,8 +21,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); - std::vector command_line_arguments = - GetCommandLineArguments(); + std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); diff --git a/packages/audioplayers/example/windows/runner/resource.h b/packages/audioplayers/example/windows/runner/resource.h index ddc7f3efc..6e9eb3386 100644 --- a/packages/audioplayers/example/windows/runner/resource.h +++ b/packages/audioplayers/example/windows/runner/resource.h @@ -2,15 +2,15 @@ // Microsoft Visual C++ generated include file. // Used by Runner.rc // -#define IDI_APP_ICON 101 +#define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/packages/audioplayers/example/windows/runner/utils.cpp b/packages/audioplayers/example/windows/runner/utils.cpp index 05b53c01b..ed0427df4 100644 --- a/packages/audioplayers/example/windows/runner/utils.cpp +++ b/packages/audioplayers/example/windows/runner/utils.cpp @@ -9,7 +9,7 @@ void CreateAndAttachConsole() { if (::AllocConsole()) { - FILE *unused; + FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } @@ -45,17 +45,16 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr); + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, utf8_string.data(), + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); diff --git a/packages/audioplayers_android/android/build.gradle b/packages/audioplayers_android/android/build.gradle index 62dd7fb49..42946e70b 100644 --- a/packages/audioplayers_android/android/build.gradle +++ b/packages/audioplayers_android/android/build.gradle @@ -53,7 +53,7 @@ android { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - + lint { disable 'InvalidPackage' } diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContext.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContextAndroid.kt similarity index 76% rename from packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContext.kt rename to packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContextAndroid.kt index a61b04405..eabbb50b9 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContext.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioContextAndroid.kt @@ -2,12 +2,16 @@ package xyz.luan.audioplayers import android.annotation.SuppressLint import android.media.AudioAttributes -import android.media.AudioAttributes.* +import android.media.AudioAttributes.Builder +import android.media.AudioAttributes.CONTENT_TYPE_MUSIC +import android.media.AudioAttributes.USAGE_MEDIA +import android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE +import android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION import android.media.AudioManager import android.media.MediaPlayer import android.os.Build import androidx.annotation.RequiresApi -import java.util.Objects +import java.util.* data class AudioContextAndroid( val isSpeakerphoneOn: Boolean, @@ -55,11 +59,11 @@ data class AudioContextAndroid( override fun hashCode() = Objects.hash(isSpeakerphoneOn, stayAwake, contentType, usageType, audioFocus, audioMode) - override fun equals(other: Any?) = (other is AudioContextAndroid) - && isSpeakerphoneOn == other.isSpeakerphoneOn - && stayAwake == other.stayAwake - && contentType == other.contentType - && usageType == other.usageType - && audioFocus == other.audioFocus - && audioMode == other.audioMode + override fun equals(other: Any?) = (other is AudioContextAndroid) && + isSpeakerphoneOn == other.isSpeakerphoneOn && + stayAwake == other.stayAwake && + contentType == other.contentType && + usageType == other.usageType && + audioFocus == other.audioFocus && + audioMode == other.audioMode } diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt index 170adb671..07bf45a94 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt @@ -1,6 +1,5 @@ package xyz.luan.audioplayers - import android.content.Context import android.media.AudioManager import android.os.Build @@ -232,7 +231,7 @@ class AudioplayersPlugin : FlutterPlugin, IUpdateCallback { handler.post { player.eventHandler.success( "audio.onDuration", - hashMapOf("value" to (player.getDuration() ?: 0)) + hashMapOf("value" to (player.getDuration() ?: 0)), ) } } @@ -265,7 +264,8 @@ class AudioplayersPlugin : FlutterPlugin, IUpdateCallback { handler.post { player.eventHandler.success("audio.onSeekComplete") player.eventHandler.success( - "audio.onCurrentPosition", hashMapOf("value" to (player.getCurrentPosition() ?: 0)) + "audio.onCurrentPosition", + hashMapOf("value" to (player.getCurrentPosition() ?: 0)), ) } } diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ByteDataSource.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ByteDataSource.kt index eff099804..1a59f4e53 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ByteDataSource.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ByteDataSource.kt @@ -6,7 +6,7 @@ import androidx.annotation.RequiresApi @RequiresApi(Build.VERSION_CODES.M) class ByteDataSource( - private val data: ByteArray + private val data: ByteArray, ) : MediaDataSource() { @Synchronized override fun getSize(): Long = data.size.toLong() @@ -32,5 +32,4 @@ class ByteDataSource( } return remainingSize.toInt() } - } diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/PlayerMode.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/PlayerMode.kt index 72532d612..d2a249e29 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/PlayerMode.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/PlayerMode.kt @@ -2,4 +2,4 @@ package xyz.luan.audioplayers enum class PlayerMode { MEDIA_PLAYER, LOW_LATENCY -} \ No newline at end of file +} diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ReleaseMode.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ReleaseMode.kt index 0bc2fcaea..cadb54b37 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ReleaseMode.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/ReleaseMode.kt @@ -2,4 +2,4 @@ package xyz.luan.audioplayers enum class ReleaseMode { RELEASE, LOOP, STOP -} \ No newline at end of file +} diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/FocusManager.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/FocusManager.kt index 0df0d15d5..0b8e5115e 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/FocusManager.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/FocusManager.kt @@ -42,7 +42,7 @@ class FocusManager( @RequiresApi(Build.VERSION_CODES.O) private fun newRequestAudioFocus(andThen: () -> Unit) { - val audioFocus = context.audioFocus ?: return andThen() + val audioFocus = context.audioFocus val audioFocusRequest = AudioFocusRequest.Builder(audioFocus) .setAudioAttributes(context.buildAttributes()) @@ -56,7 +56,7 @@ class FocusManager( @Deprecated("Use requestAudioFocus instead") private fun oldRequestAudioFocus(andThen: () -> Unit) { - val audioFocus = context.audioFocus ?: return andThen() + val audioFocus = context.audioFocus audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { handleFocusResult(it, andThen) } @Suppress("DEPRECATION") val result = audioManager.requestAudioFocus( diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/SoundPoolPlayer.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/SoundPoolPlayer.kt index 5f72c3a78..c310051cf 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/SoundPoolPlayer.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/SoundPoolPlayer.kt @@ -10,7 +10,7 @@ import xyz.luan.audioplayers.source.Source import xyz.luan.audioplayers.source.UrlSource import java.util.Collections.synchronizedMap -/// Value should not exceed 32 +/** Value should not exceed 32 */ // TODO(luan): make this configurable private const val MAX_STREAMS = 32 diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/WrappedPlayer.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/WrappedPlayer.kt index 7c0871355..21cc9a964 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/WrappedPlayer.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/player/WrappedPlayer.kt @@ -3,11 +3,15 @@ package xyz.luan.audioplayers.player import android.content.Context import android.media.AudioManager import android.media.MediaPlayer -import kotlin.math.min -import xyz.luan.audioplayers.* +import xyz.luan.audioplayers.AudioContextAndroid +import xyz.luan.audioplayers.AudioplayersPlugin +import xyz.luan.audioplayers.EventHandler +import xyz.luan.audioplayers.PlayerMode import xyz.luan.audioplayers.PlayerMode.LOW_LATENCY import xyz.luan.audioplayers.PlayerMode.MEDIA_PLAYER +import xyz.luan.audioplayers.ReleaseMode import xyz.luan.audioplayers.source.Source +import kotlin.math.min // For some reason this cannot be accessed from MediaPlayer.MEDIA_ERROR_SYSTEM private const val MEDIA_ERROR_SYSTEM = -2147483648 @@ -137,8 +141,9 @@ class WrappedPlayer internal constructor( if (context == audioContext) { return } - if (context.audioFocus != AudioManager.AUDIOFOCUS_NONE - && audioContext.audioFocus == AudioManager.AUDIOFOCUS_NONE) { + if (context.audioFocus != AudioManager.AUDIOFOCUS_NONE && + audioContext.audioFocus == AudioManager.AUDIOFOCUS_NONE + ) { focusManager.handleStop() } this.context = audioContext.copy() diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/BytesSource.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/BytesSource.kt index 3e1545f98..eec5bd648 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/BytesSource.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/BytesSource.kt @@ -9,8 +9,8 @@ import xyz.luan.audioplayers.player.SoundPoolPlayer @RequiresApi(Build.VERSION_CODES.M) data class BytesSource( val dataSource: ByteDataSource, -): Source { - constructor(bytes: ByteArray): this(ByteDataSource(bytes)) +) : Source { + constructor(bytes: ByteArray) : this(ByteDataSource(bytes)) override fun setForMediaPlayer(mediaPlayer: MediaPlayer) { mediaPlayer.setDataSource(dataSource) @@ -19,4 +19,4 @@ data class BytesSource( override fun setForSoundPool(soundPoolPlayer: SoundPoolPlayer) { error("Bytes sources are not supported on LOW_LATENCY mode yet.") } -} \ No newline at end of file +} diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/Source.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/Source.kt index 17de82d14..8d410bed7 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/Source.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/Source.kt @@ -7,4 +7,4 @@ import xyz.luan.audioplayers.player.SoundPoolPlayer interface Source { fun setForMediaPlayer(mediaPlayer: MediaPlayer) fun setForSoundPool(soundPoolPlayer: SoundPoolPlayer) -} \ No newline at end of file +} diff --git a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/UrlSource.kt b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/UrlSource.kt index 2e3cd967f..481bd22f5 100644 --- a/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/UrlSource.kt +++ b/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/source/UrlSource.kt @@ -11,7 +11,7 @@ import java.net.URL data class UrlSource( val url: String, val isLocal: Boolean, -): Source { +) : Source { override fun setForMediaPlayer(mediaPlayer: MediaPlayer) { mediaPlayer.setDataSource(url) } @@ -20,7 +20,7 @@ data class UrlSource( soundPoolPlayer.setUrlSource(this) } - fun getAudioPathForSoundPool(): String? { + fun getAudioPathForSoundPool(): String { if (isLocal) { return url.removePrefix("file://") } @@ -49,4 +49,4 @@ data class UrlSource( } return outputStream.toByteArray() } -} \ No newline at end of file +} diff --git a/packages/audioplayers_android/android/src/test/kotlin/xyz/luan/audioplayers/ToConstantCaseTest.kt b/packages/audioplayers_android/android/src/test/kotlin/xyz/luan/audioplayers/ToConstantCaseTest.kt index 5e66bdd98..9c6cb85f5 100644 --- a/packages/audioplayers_android/android/src/test/kotlin/xyz/luan/audioplayers/ToConstantCaseTest.kt +++ b/packages/audioplayers_android/android/src/test/kotlin/xyz/luan/audioplayers/ToConstantCaseTest.kt @@ -1,7 +1,7 @@ package xyz.luan.audioplayers -import org.junit.jupiter.api.Test import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test internal class ToConstantCaseTest { @Test diff --git a/packages/audioplayers_darwin/ios/Classes/AudioplayersDarwinPlugin.h b/packages/audioplayers_darwin/ios/Classes/AudioplayersDarwinPlugin.h index edf06ff0f..5557d027d 100644 --- a/packages/audioplayers_darwin/ios/Classes/AudioplayersDarwinPlugin.h +++ b/packages/audioplayers_darwin/ios/Classes/AudioplayersDarwinPlugin.h @@ -1,4 +1,4 @@ #import -@interface AudioplayersDarwinPlugin : NSObject +@interface AudioplayersDarwinPlugin : NSObject @end diff --git a/packages/audioplayers_darwin/macos/Classes/AudioplayersDarwinPlugin.h b/packages/audioplayers_darwin/macos/Classes/AudioplayersDarwinPlugin.h index fbc7183c2..a9699303b 100644 --- a/packages/audioplayers_darwin/macos/Classes/AudioplayersDarwinPlugin.h +++ b/packages/audioplayers_darwin/macos/Classes/AudioplayersDarwinPlugin.h @@ -1,4 +1,4 @@ #import -@interface AudioplayersDarwinPlugin : NSObject +@interface AudioplayersDarwinPlugin : NSObject @end diff --git a/packages/audioplayers_linux/linux/audio_player.cc b/packages/audioplayers_linux/linux/audio_player.cc index 19e79b782..586043106 100644 --- a/packages/audioplayers_linux/linux/audio_player.cc +++ b/packages/audioplayers_linux/linux/audio_player.cc @@ -2,295 +2,315 @@ #include -AudioPlayer::AudioPlayer(std::string playerId, FlMethodChannel *methodChannel, - FlEventChannel *eventChannel) - : _playerId(playerId), - _eventChannel(eventChannel) { - // GStreamer lib only needs to be initialized once, but doing it while registering the plugin can be problematic as - // it likely needs a GUI to be present. Calling it multiple times is fine. - gst_init(NULL, NULL); - - playbin = gst_element_factory_make("playbin", NULL); - if (!playbin) { - throw "Not all elements could be created."; - } - - // Setup stereo balance controller - panorama = gst_element_factory_make("audiopanorama", NULL); - if (panorama) { - audiobin = gst_bin_new(NULL); - audiosink = gst_element_factory_make("autoaudiosink", NULL); - - gst_bin_add_many(GST_BIN(audiobin), panorama, audiosink, NULL); - gst_element_link(panorama, audiosink); - - GstPad *sinkpad = gst_element_get_static_pad(panorama, "sink"); - panoramaSinkPad = gst_ghost_pad_new("sink", sinkpad); - gst_element_add_pad(audiobin, panoramaSinkPad); - gst_object_unref(GST_OBJECT(sinkpad)); - - g_object_set(G_OBJECT(playbin), "audio-sink", audiobin, NULL); - g_object_set(G_OBJECT(panorama), "method", 1, NULL); - } - - // Setup source options - g_signal_connect(playbin, "source-setup", - G_CALLBACK(AudioPlayer::SourceSetup), &source); - - bus = gst_element_get_bus(playbin); - - // Watch bus messages for one time events - gst_bus_add_watch(bus, (GstBusFunc)AudioPlayer::OnBusMessage, this); - - // Refresh continuously to emit reoccurring events - _refreshId = g_timeout_add(250, (GSourceFunc)AudioPlayer::OnRefresh, this); +AudioPlayer::AudioPlayer(std::string playerId, + FlMethodChannel* methodChannel, + FlEventChannel* eventChannel) + : _playerId(playerId), _eventChannel(eventChannel) { + // GStreamer lib only needs to be initialized once, but doing it while + // registering the plugin can be problematic as it likely needs a GUI to be + // present. Calling it multiple times is fine. + gst_init(NULL, NULL); + + playbin = gst_element_factory_make("playbin", NULL); + if (!playbin) { + throw "Not all elements could be created."; + } + + // Setup stereo balance controller + panorama = gst_element_factory_make("audiopanorama", NULL); + if (panorama) { + audiobin = gst_bin_new(NULL); + audiosink = gst_element_factory_make("autoaudiosink", NULL); + + gst_bin_add_many(GST_BIN(audiobin), panorama, audiosink, NULL); + gst_element_link(panorama, audiosink); + + GstPad* sinkpad = gst_element_get_static_pad(panorama, "sink"); + panoramaSinkPad = gst_ghost_pad_new("sink", sinkpad); + gst_element_add_pad(audiobin, panoramaSinkPad); + gst_object_unref(GST_OBJECT(sinkpad)); + + g_object_set(G_OBJECT(playbin), "audio-sink", audiobin, NULL); + g_object_set(G_OBJECT(panorama), "method", 1, NULL); + } + + // Setup source options + g_signal_connect(playbin, "source-setup", + G_CALLBACK(AudioPlayer::SourceSetup), &source); + + bus = gst_element_get_bus(playbin); + + // Watch bus messages for one time events + gst_bus_add_watch(bus, (GstBusFunc)AudioPlayer::OnBusMessage, this); + + // Refresh continuously to emit reoccurring events + _refreshId = g_timeout_add(250, (GSourceFunc)AudioPlayer::OnRefresh, this); } AudioPlayer::~AudioPlayer() {} -void AudioPlayer::SourceSetup(GstElement *playbin, GstElement *source, - GstElement **p_src) { - // Allow sources from unencrypted / misconfigured connections - if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), - "ssl-strict") != 0) { - g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL); - } +void AudioPlayer::SourceSetup(GstElement* playbin, + GstElement* source, + GstElement** p_src) { + // Allow sources from unencrypted / misconfigured connections + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != + 0) { + g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL); + } }; void AudioPlayer::SetSourceUrl(std::string url) { - if (_url != url) { - _url = url; - gst_element_set_state(playbin, GST_STATE_NULL); - _isInitialized = false; - _isPlaying = false; - if (!_url.empty()) { - g_object_set(GST_OBJECT(playbin), "uri", _url.c_str(), NULL); - if (playbin->current_state != GST_STATE_READY) { - GstStateChangeReturn ret = gst_element_set_state(playbin, GST_STATE_READY); - if (ret == GST_STATE_CHANGE_FAILURE) { - throw "Unable to set the pipeline to GST_STATE_READY."; - } - } + if (_url != url) { + _url = url; + gst_element_set_state(playbin, GST_STATE_NULL); + _isInitialized = false; + _isPlaying = false; + if (!_url.empty()) { + g_object_set(GST_OBJECT(playbin), "uri", _url.c_str(), NULL); + if (playbin->current_state != GST_STATE_READY) { + GstStateChangeReturn ret = + gst_element_set_state(playbin, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + throw "Unable to set the pipeline to GST_STATE_READY."; } - } else { - this->OnPrepared(true); + } } + } else { + this->OnPrepared(true); + } } void AudioPlayer::ReleaseMediaSource() { - if(_isPlaying) _isPlaying = false; - if(_isInitialized) _isInitialized = false; - _url.clear(); - - GstState playbinState; - gst_element_get_state(playbin, &playbinState, NULL, GST_CLOCK_TIME_NONE); - if(playbinState > GST_STATE_NULL) { - gst_element_set_state(playbin, GST_STATE_NULL); - } + if (_isPlaying) + _isPlaying = false; + if (_isInitialized) + _isInitialized = false; + _url.clear(); + + GstState playbinState; + gst_element_get_state(playbin, &playbinState, NULL, GST_CLOCK_TIME_NONE); + if (playbinState > GST_STATE_NULL) { + gst_element_set_state(playbin, GST_STATE_NULL); + } } -gboolean AudioPlayer::OnBusMessage(GstBus *bus, GstMessage *message, - AudioPlayer *data) { - switch (GST_MESSAGE_TYPE(message)) { - case GST_MESSAGE_ERROR: { - GError *err; - gchar *debug; - - gst_message_parse_error(message, &err, &debug); - data->OnMediaError(err, debug); - g_error_free(err); - g_free(debug); - break; - } - case GST_MESSAGE_STATE_CHANGED: - GstState old_state, new_state; - - gst_message_parse_state_changed(message, &old_state, &new_state, - NULL); - data->OnMediaStateChange(GST_MESSAGE_SRC(message), &old_state, &new_state); - break; - case GST_MESSAGE_EOS: - data->OnPlaybackEnded(); - break; - case GST_MESSAGE_DURATION_CHANGED: - data->OnDurationUpdate(); - break; - case GST_MESSAGE_ASYNC_DONE: - if (!data->_isSeekCompleted) { - data->OnSeekCompleted(); - data->_isSeekCompleted = true; - } - break; - default: - // For more GstMessage types see: - // https://gstreamer.freedesktop.org/documentation/gstreamer/gstmessage.html?gi-language=c#enumerations - break; - } - - // Continue watching for messages - return TRUE; +gboolean AudioPlayer::OnBusMessage(GstBus* bus, + GstMessage* message, + AudioPlayer* data) { + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_ERROR: { + GError* err; + gchar* debug; + + gst_message_parse_error(message, &err, &debug); + data->OnMediaError(err, debug); + g_error_free(err); + g_free(debug); + break; + } + case GST_MESSAGE_STATE_CHANGED: + GstState old_state, new_state; + + gst_message_parse_state_changed(message, &old_state, &new_state, NULL); + data->OnMediaStateChange(GST_MESSAGE_SRC(message), &old_state, + &new_state); + break; + case GST_MESSAGE_EOS: + data->OnPlaybackEnded(); + break; + case GST_MESSAGE_DURATION_CHANGED: + data->OnDurationUpdate(); + break; + case GST_MESSAGE_ASYNC_DONE: + if (!data->_isSeekCompleted) { + data->OnSeekCompleted(); + data->_isSeekCompleted = true; + } + break; + default: + // For more GstMessage types see: + // https://gstreamer.freedesktop.org/documentation/gstreamer/gstmessage.html?gi-language=c#enumerations + break; + } + + // Continue watching for messages + return TRUE; }; // Compare with refresh_ui in // https://gstreamer.freedesktop.org/documentation/tutorials/basic/toolkit-integration.html?gi-language=c#walkthrough -gboolean AudioPlayer::OnRefresh(AudioPlayer *data) { - if(data->playbin == nullptr) { - return FALSE; - } - // We do not want to update anything unless we are in PLAYING state - GstState playbinState; - gst_element_get_state(data->playbin, &playbinState, NULL, GST_CLOCK_TIME_NONE); - if (playbinState == GST_STATE_PLAYING) { - data->OnPositionUpdate(); - } - return TRUE; +gboolean AudioPlayer::OnRefresh(AudioPlayer* data) { + if (data->playbin == nullptr) { + return FALSE; + } + // We do not want to update anything unless we are in PLAYING state + GstState playbinState; + gst_element_get_state(data->playbin, &playbinState, NULL, + GST_CLOCK_TIME_NONE); + if (playbinState == GST_STATE_PLAYING) { + data->OnPositionUpdate(); + } + return TRUE; } -void AudioPlayer::OnMediaError(GError *error, gchar *debug) { - if (this->_eventChannel) { - this->OnError(std::to_string(error->code).c_str(), error->message, - nullptr, &error); - } +void AudioPlayer::OnMediaError(GError* error, gchar* debug) { + if (this->_eventChannel) { + this->OnError(std::to_string(error->code).c_str(), error->message, nullptr, + &error); + } } -void AudioPlayer::OnError(const gchar *code, const gchar *message, - FlValue *details, GError **error) { - if (this->_eventChannel) { - fl_event_channel_send_error(this->_eventChannel, code, message, details, - nullptr, error); - } else { - std::ostringstream oss; - oss << "Error: " << code << "; message=" << message; - g_print("%s\n", oss.str().c_str()); - } +void AudioPlayer::OnError(const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + if (this->_eventChannel) { + fl_event_channel_send_error(this->_eventChannel, code, message, details, + nullptr, error); + } else { + std::ostringstream oss; + oss << "Error: " << code << "; message=" << message; + g_print("%s\n", oss.str().c_str()); + } } -void AudioPlayer::OnMediaStateChange(GstObject *src, GstState *old_state, - GstState *new_state) { - if (!playbin) { - this->OnError("LinuxAudioError", "Player was already disposed (OnMediaStateChange).", nullptr, nullptr); - return; - } - - if (src == GST_OBJECT(playbin)) { - if (*new_state == GST_STATE_READY) { - if (this->_isInitialized) { - this->_isInitialized = false; - } - // Need to set to pause state, in order to make player functional - GstStateChangeReturn ret = gst_element_set_state(playbin, GST_STATE_PAUSED); - if (ret == GST_STATE_CHANGE_FAILURE) { - this->OnError("LinuxAudioError", "Unable to set the pipeline from GST_STATE_READY to GST_STATE_PAUSED.", nullptr, nullptr); - } - } else if (*old_state == GST_STATE_PAUSED && *new_state == GST_STATE_PLAYING) { - OnPositionUpdate(); - OnDurationUpdate(); - } else if (*new_state >= GST_STATE_PAUSED) { - if (!this->_isInitialized) { - this->_isInitialized = true; - this->OnPrepared(true); - if (this->_isPlaying) { - Resume(); - } - } - } else if (this->_isInitialized) { - this->_isInitialized = false; +void AudioPlayer::OnMediaStateChange(GstObject* src, + GstState* old_state, + GstState* new_state) { + if (!playbin) { + this->OnError("LinuxAudioError", + "Player was already disposed (OnMediaStateChange).", nullptr, + nullptr); + return; + } + + if (src == GST_OBJECT(playbin)) { + if (*new_state == GST_STATE_READY) { + if (this->_isInitialized) { + this->_isInitialized = false; + } + // Need to set to pause state, in order to make player functional + GstStateChangeReturn ret = + gst_element_set_state(playbin, GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_FAILURE) { + this->OnError("LinuxAudioError", + "Unable to set the pipeline from GST_STATE_READY to " + "GST_STATE_PAUSED.", + nullptr, nullptr); + } + } else if (*old_state == GST_STATE_PAUSED && + *new_state == GST_STATE_PLAYING) { + OnPositionUpdate(); + OnDurationUpdate(); + } else if (*new_state >= GST_STATE_PAUSED) { + if (!this->_isInitialized) { + this->_isInitialized = true; + this->OnPrepared(true); + if (this->_isPlaying) { + Resume(); } + } + } else if (this->_isInitialized) { + this->_isInitialized = false; } + } } void AudioPlayer::OnPrepared(bool isPrepared) { - if (this->_eventChannel) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", - fl_value_new_string("audio.onPrepared")); - fl_value_set_string(map, "value", fl_value_new_bool(isPrepared)); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } + if (this->_eventChannel) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", fl_value_new_string("audio.onPrepared")); + fl_value_set_string(map, "value", fl_value_new_bool(isPrepared)); + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } } void AudioPlayer::OnPositionUpdate() { - if (this->_eventChannel) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", - fl_value_new_string("audio.onCurrentPosition")); - fl_value_set_string(map, "value", fl_value_new_int(GetPosition().value_or(0))); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } + if (this->_eventChannel) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", + fl_value_new_string("audio.onCurrentPosition")); + fl_value_set_string(map, "value", + fl_value_new_int(GetPosition().value_or(0))); + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } } void AudioPlayer::OnDurationUpdate() { - if (this->_eventChannel) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", - fl_value_new_string("audio.onDuration")); - fl_value_set_string(map, "value", fl_value_new_int(GetDuration().value_or(0))); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } + if (this->_eventChannel) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", fl_value_new_string("audio.onDuration")); + fl_value_set_string(map, "value", + fl_value_new_int(GetDuration().value_or(0))); + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } } void AudioPlayer::OnSeekCompleted() { - if (this->_eventChannel) { - OnPositionUpdate(); - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", - fl_value_new_string("audio.onSeekComplete")); - fl_value_set_string(map, "value", fl_value_new_bool(true)); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } + if (this->_eventChannel) { + OnPositionUpdate(); + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", + fl_value_new_string("audio.onSeekComplete")); + fl_value_set_string(map, "value", fl_value_new_bool(true)); + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } } void AudioPlayer::OnPlaybackEnded() { - if (this->_eventChannel) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", - fl_value_new_string("audio.onComplete")); - fl_value_set_string(map, "value", fl_value_new_bool(true)); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } - if (GetLooping()) { - Play(); - } else { - Pause(); - SetPosition(0); - } + if (this->_eventChannel) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", fl_value_new_string("audio.onComplete")); + fl_value_set_string(map, "value", fl_value_new_bool(true)); + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } + if (GetLooping()) { + Play(); + } else { + Pause(); + SetPosition(0); + } } -void AudioPlayer::OnLog(const gchar *message) { - if (this->_eventChannel) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", fl_value_new_string("audio.onLog")); - fl_value_set_string(map, "value", fl_value_new_string(message)); +void AudioPlayer::OnLog(const gchar* message) { + if (this->_eventChannel) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", fl_value_new_string("audio.onLog")); + fl_value_set_string(map, "value", fl_value_new_string(message)); - fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); - } + fl_event_channel_send(this->_eventChannel, map, nullptr, nullptr); + } } void AudioPlayer::SetBalance(float balance) { - if (!panorama) { - this->OnLog("Audiopanorama was not initialized"); - return; - } - - if (balance > 1.0f) { - balance = 1.0f; - } else if (balance < -1.0f) { - balance = -1.0f; - } - g_object_set(G_OBJECT(panorama), "panorama", balance, NULL); + if (!panorama) { + this->OnLog("Audiopanorama was not initialized"); + return; + } + + if (balance > 1.0f) { + balance = 1.0f; + } else if (balance < -1.0f) { + balance = -1.0f; + } + g_object_set(G_OBJECT(panorama), "panorama", balance, NULL); } -void AudioPlayer::SetLooping(bool isLooping) { _isLooping = isLooping; } +void AudioPlayer::SetLooping(bool isLooping) { + _isLooping = isLooping; +} -bool AudioPlayer::GetLooping() { return _isLooping; } +bool AudioPlayer::GetLooping() { + return _isLooping; +} void AudioPlayer::SetVolume(double volume) { - if (volume > 1) { - volume = 1; - } else if (volume < 0) { - volume = 0; - } - g_object_set(G_OBJECT(playbin), "volume", volume, NULL); + if (volume > 1) { + volume = 1; + } else if (volume < 0) { + volume = 0; + } + g_object_set(G_OBJECT(playbin), "volume", volume, NULL); } /** @@ -302,158 +322,158 @@ void AudioPlayer::SetVolume(double volume) { * @param rate the playback rate (speed) */ void AudioPlayer::SetPlayback(int64_t position, double rate) { - if (rate != 0 && _playbackRate != rate) { - _playbackRate = rate; - } - - if (!_isInitialized) { - return; - } - // See: - // https://gstreamer.freedesktop.org/documentation/tutorials/basic/playback-speed.html?gi-language=c - if (!_isSeekCompleted) { - return; - } - if (rate == 0) { - // Do not set rate if it's 0, rather pause. - Pause(); - return; - } - - _isSeekCompleted = false; - - GstEvent *seek_event; - if (rate > 0) { - seek_event = gst_event_new_seek( - rate, GST_FORMAT_TIME, - GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), - GST_SEEK_TYPE_SET, position * GST_MSECOND, GST_SEEK_TYPE_NONE, -1); - } else { - seek_event = gst_event_new_seek( - rate, GST_FORMAT_TIME, - GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), - GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position * GST_MSECOND); - } - - if (!gst_element_send_event(playbin, seek_event)) { - this->OnLog((std::string("Could not set playback to position ") + - std::to_string(position) + std::string(" and rate ") + - std::to_string(rate) + std::string(".")) - .c_str()); - _isSeekCompleted = true; - } + if (rate != 0 && _playbackRate != rate) { + _playbackRate = rate; + } + + if (!_isInitialized) { + return; + } + // See: + // https://gstreamer.freedesktop.org/documentation/tutorials/basic/playback-speed.html?gi-language=c + if (!_isSeekCompleted) { + return; + } + if (rate == 0) { + // Do not set rate if it's 0, rather pause. + Pause(); + return; + } + + _isSeekCompleted = false; + + GstEvent* seek_event; + if (rate > 0) { + seek_event = gst_event_new_seek( + rate, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, position * GST_MSECOND, GST_SEEK_TYPE_NONE, -1); + } else { + seek_event = gst_event_new_seek( + rate, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position * GST_MSECOND); + } + + if (!gst_element_send_event(playbin, seek_event)) { + this->OnLog((std::string("Could not set playback to position ") + + std::to_string(position) + std::string(" and rate ") + + std::to_string(rate) + std::string(".")) + .c_str()); + _isSeekCompleted = true; + } } void AudioPlayer::SetPlaybackRate(double rate) { - SetPlayback(GetPosition().value_or(0), rate); + SetPlayback(GetPosition().value_or(0), rate); } /** * @param position the position in milliseconds */ void AudioPlayer::SetPosition(int64_t position) { - if (!_isInitialized) { - return; - } - SetPlayback(position, _playbackRate); + if (!_isInitialized) { + return; + } + SetPlayback(position, _playbackRate); } /** * @return int64_t the position in milliseconds */ std::optional AudioPlayer::GetPosition() { - gint64 current = 0; - if (!gst_element_query_position(playbin, GST_FORMAT_TIME, ¤t)) { - this->OnLog("Could not query current position."); - return std::nullopt; - } - return std::make_optional(current / 1000000); + gint64 current = 0; + if (!gst_element_query_position(playbin, GST_FORMAT_TIME, ¤t)) { + this->OnLog("Could not query current position."); + return std::nullopt; + } + return std::make_optional(current / 1000000); } /** * @return int64_t the duration in milliseconds */ std::optional AudioPlayer::GetDuration() { - gint64 duration = 0; - if (!gst_element_query_duration(playbin, GST_FORMAT_TIME, &duration)) { - // FIXME: Get duration for MP3 with variable bit rate with gst-discoverer: - // https://gstreamer.freedesktop.org/documentation/pbutils/gstdiscoverer.html?gi-language=c#gst_discoverer_info_get_duration - this->OnLog("Could not query current duration."); - return std::nullopt; - } - return std::make_optional(duration / 1000000); + gint64 duration = 0; + if (!gst_element_query_duration(playbin, GST_FORMAT_TIME, &duration)) { + // FIXME: Get duration for MP3 with variable bit rate with gst-discoverer: + // https://gstreamer.freedesktop.org/documentation/pbutils/gstdiscoverer.html?gi-language=c#gst_discoverer_info_get_duration + this->OnLog("Could not query current duration."); + return std::nullopt; + } + return std::make_optional(duration / 1000000); } void AudioPlayer::Play() { - SetPosition(0); - Resume(); + SetPosition(0); + Resume(); } void AudioPlayer::Pause() { - if (_isPlaying) { - _isPlaying = false; - } - if (!_isInitialized) { - return; - } - GstStateChangeReturn ret = gst_element_set_state(playbin, GST_STATE_PAUSED); - if (ret == GST_STATE_CHANGE_SUCCESS) { - OnPositionUpdate(); // Update to exact position when pausing - } else if (ret == GST_STATE_CHANGE_FAILURE) { - throw "Unable to set the pipeline to GST_STATE_PAUSED."; - } + if (_isPlaying) { + _isPlaying = false; + } + if (!_isInitialized) { + return; + } + GstStateChangeReturn ret = gst_element_set_state(playbin, GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_SUCCESS) { + OnPositionUpdate(); // Update to exact position when pausing + } else if (ret == GST_STATE_CHANGE_FAILURE) { + throw "Unable to set the pipeline to GST_STATE_PAUSED."; + } } void AudioPlayer::Resume() { - if (!_isPlaying) { - _isPlaying = true; - } - if (!_isInitialized) { - return; - } - GstStateChangeReturn ret = - gst_element_set_state(playbin, GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_SUCCESS) { - // Update position and duration when start playing, as no event is emitted - // elsewhere - OnPositionUpdate(); - OnDurationUpdate(); - } else if (ret == GST_STATE_CHANGE_FAILURE) { - throw "Unable to set the pipeline to GST_STATE_PLAYING."; - } + if (!_isPlaying) { + _isPlaying = true; + } + if (!_isInitialized) { + return; + } + GstStateChangeReturn ret = gst_element_set_state(playbin, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_SUCCESS) { + // Update position and duration when start playing, as no event is emitted + // elsewhere + OnPositionUpdate(); + OnDurationUpdate(); + } else if (ret == GST_STATE_CHANGE_FAILURE) { + throw "Unable to set the pipeline to GST_STATE_PLAYING."; + } } void AudioPlayer::Dispose() { - if(!playbin) throw "Player was already disposed (Dispose)"; - ReleaseMediaSource(); - - g_source_remove(_refreshId); - - if(bus) { - gst_bus_remove_watch(bus); - gst_object_unref(GST_OBJECT(bus)); - bus = nullptr; - } - - if(source) { - gst_object_unref(GST_OBJECT(source)); - source = nullptr; - } - - if(panorama) { - gst_element_set_state(audiobin, GST_STATE_NULL); - - gst_element_remove_pad(audiobin, panoramaSinkPad); - gst_bin_remove(GST_BIN(audiobin), audiosink); - gst_bin_remove(GST_BIN(audiobin), panorama); - - // audiobin gets unreferenced (2x) via playbin - panorama = nullptr; - } - - gst_object_unref(GST_OBJECT(playbin)); - // Do not dispose method channel as it is used by multiple players! - g_clear_object(&_eventChannel); - _eventChannel = nullptr; - playbin = nullptr; + if (!playbin) + throw "Player was already disposed (Dispose)"; + ReleaseMediaSource(); + + g_source_remove(_refreshId); + + if (bus) { + gst_bus_remove_watch(bus); + gst_object_unref(GST_OBJECT(bus)); + bus = nullptr; + } + + if (source) { + gst_object_unref(GST_OBJECT(source)); + source = nullptr; + } + + if (panorama) { + gst_element_set_state(audiobin, GST_STATE_NULL); + + gst_element_remove_pad(audiobin, panoramaSinkPad); + gst_bin_remove(GST_BIN(audiobin), audiosink); + gst_bin_remove(GST_BIN(audiobin), panorama); + + // audiobin gets unreferenced (2x) via playbin + panorama = nullptr; + } + + gst_object_unref(GST_OBJECT(playbin)); + // Do not dispose method channel as it is used by multiple players! + g_clear_object(&_eventChannel); + _eventChannel = nullptr; + playbin = nullptr; } diff --git a/packages/audioplayers_linux/linux/audio_player.h b/packages/audioplayers_linux/linux/audio_player.h index 312d2efeb..6d648415b 100644 --- a/packages/audioplayers_linux/linux/audio_player.h +++ b/packages/audioplayers_linux/linux/audio_player.h @@ -21,88 +21,94 @@ extern "C" { } class AudioPlayer { - public: - AudioPlayer(std::string playerId, FlMethodChannel *methodChannel, - FlEventChannel *eventChannel); + public: + AudioPlayer(std::string playerId, + FlMethodChannel* methodChannel, + FlEventChannel* eventChannel); - std::optional GetPosition(); + std::optional GetPosition(); - std::optional GetDuration(); + std::optional GetDuration(); - bool GetLooping(); + bool GetLooping(); - void Play(); + void Play(); - void Pause(); + void Pause(); - void Resume(); + void Resume(); - void Dispose(); + void Dispose(); - void SetBalance(float balance); + void SetBalance(float balance); - void SetLooping(bool isLooping); + void SetLooping(bool isLooping); - void SetVolume(double volume); + void SetVolume(double volume); - void SetPlaybackRate(double rate); + void SetPlaybackRate(double rate); - void SetPosition(int64_t position); + void SetPosition(int64_t position); - void SetSourceUrl(std::string url); + void SetSourceUrl(std::string url); - void ReleaseMediaSource(); + void ReleaseMediaSource(); - void OnError(const gchar *code, const gchar *message, FlValue *details, - GError **error); + void OnError(const gchar* code, + const gchar* message, + FlValue* details, + GError** error); - void OnLog(const gchar *message); + void OnLog(const gchar* message); - virtual ~AudioPlayer(); + virtual ~AudioPlayer(); - private: - // Gst members - GstElement *playbin = nullptr; - GstElement *source = nullptr; - GstElement *panorama = nullptr; - GstElement *audiobin = nullptr; - GstElement *audiosink = nullptr; - GstPad *panoramaSinkPad = nullptr; - GstBus *bus = nullptr; + private: + // Gst members + GstElement* playbin = nullptr; + GstElement* source = nullptr; + GstElement* panorama = nullptr; + GstElement* audiobin = nullptr; + GstElement* audiosink = nullptr; + GstPad* panoramaSinkPad = nullptr; + GstBus* bus = nullptr; - bool _isInitialized = false; - bool _isPlaying = false; - bool _isLooping = false; - bool _isSeekCompleted = true; - double _playbackRate = 1.0; - guint _refreshId; + bool _isInitialized = false; + bool _isPlaying = false; + bool _isLooping = false; + bool _isSeekCompleted = true; + double _playbackRate = 1.0; + guint _refreshId; - std::string _url{}; - std::string _playerId; - FlEventChannel *_eventChannel; + std::string _url{}; + std::string _playerId; + FlEventChannel* _eventChannel; - static void SourceSetup(GstElement *playbin, GstElement *source, - GstElement **p_src); + static void SourceSetup(GstElement* playbin, + GstElement* source, + GstElement** p_src); - static gboolean OnBusMessage(GstBus *bus, GstMessage *message, - AudioPlayer *data); + static gboolean OnBusMessage(GstBus* bus, + GstMessage* message, + AudioPlayer* data); - static gboolean OnRefresh(AudioPlayer *data); + static gboolean OnRefresh(AudioPlayer* data); - void SetPlayback(int64_t seekTo, double rate); + void SetPlayback(int64_t seekTo, double rate); - void OnMediaError(GError *error, gchar *debug); + void OnMediaError(GError* error, gchar* debug); - void OnMediaStateChange(GstObject *src, GstState *old_state, - GstState *new_state); + void OnMediaStateChange(GstObject* src, + GstState* old_state, + GstState* new_state); - void OnPositionUpdate(); + void OnPositionUpdate(); - void OnDurationUpdate(); + void OnDurationUpdate(); - void OnSeekCompleted(); + void OnSeekCompleted(); - void OnPlaybackEnded(); + void OnPlaybackEnded(); - void OnPrepared(bool isPrepared); + void OnPrepared(bool isPrepared); }; diff --git a/packages/audioplayers_linux/linux/audioplayers_linux_plugin.cc b/packages/audioplayers_linux/linux/audioplayers_linux_plugin.cc index c478bd0bb..d23b970dd 100644 --- a/packages/audioplayers_linux/linux/audioplayers_linux_plugin.cc +++ b/packages/audioplayers_linux/linux/audioplayers_linux_plugin.cc @@ -15,292 +15,295 @@ #include "audio_player.h" -#define AUDIOPLAYERS_LINUX_PLUGIN(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), audioplayers_linux_plugin_get_type(), \ - AudioplayersLinuxPlugin)) +#define AUDIOPLAYERS_LINUX_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), audioplayers_linux_plugin_get_type(), \ + AudioplayersLinuxPlugin)) struct _AudioplayersLinuxPlugin { - GObject parent_instance; + GObject parent_instance; }; -G_DEFINE_TYPE(AudioplayersLinuxPlugin, audioplayers_linux_plugin, +G_DEFINE_TYPE(AudioplayersLinuxPlugin, + audioplayers_linux_plugin, g_object_get_type()) -static FlBinaryMessenger *binaryMessenger; -static FlMethodChannel *methods; -static FlMethodChannel *globalMethods; -static FlEventChannel *globalEvents; +static FlBinaryMessenger* binaryMessenger; +static FlMethodChannel* methods; +static FlMethodChannel* globalMethods; +static FlEventChannel* globalEvents; static std::map> audioPlayers; static void audioplayers_linux_plugin_create_player( - const std::string &playerId) { - g_autoptr(FlStandardMethodCodec) eventCodec = - fl_standard_method_codec_new(); - auto eventChannel = fl_event_channel_new( - binaryMessenger, ("xyz.luan/audioplayers/events/" + playerId).c_str(), - FL_METHOD_CODEC(eventCodec)); - - auto player = - std::make_unique(playerId, methods, eventChannel); - audioPlayers.insert(std::make_pair(playerId, std::move(player))); + const std::string& playerId) { + g_autoptr(FlStandardMethodCodec) eventCodec = fl_standard_method_codec_new(); + auto eventChannel = fl_event_channel_new( + binaryMessenger, ("xyz.luan/audioplayers/events/" + playerId).c_str(), + FL_METHOD_CODEC(eventCodec)); + + auto player = std::make_unique(playerId, methods, eventChannel); + audioPlayers.insert(std::make_pair(playerId, std::move(player))); } -static AudioPlayer *audioplayers_linux_plugin_get_player( - const std::string &playerId) { - auto searchPlayer = audioPlayers.find(playerId); - if(searchPlayer == audioPlayers.end()) { - return nullptr; - } - return searchPlayer->second.get(); +static AudioPlayer* audioplayers_linux_plugin_get_player( + const std::string& playerId) { + auto searchPlayer = audioPlayers.find(playerId); + if (searchPlayer == audioPlayers.end()) { + return nullptr; + } + return searchPlayer->second.get(); } -static void audioplayers_linux_plugin_on_global_log(const gchar *message) { - g_autoptr(FlValue) map = fl_value_new_map(); - fl_value_set_string(map, "event", fl_value_new_string("audio.onLog")); - fl_value_set_string(map, "value", fl_value_new_string(message)); +static void audioplayers_linux_plugin_on_global_log(const gchar* message) { + g_autoptr(FlValue) map = fl_value_new_map(); + fl_value_set_string(map, "event", fl_value_new_string("audio.onLog")); + fl_value_set_string(map, "value", fl_value_new_string(message)); - fl_event_channel_send(globalEvents, map, nullptr, nullptr); + fl_event_channel_send(globalEvents, map, nullptr, nullptr); } static void audioplayers_linux_plugin_handle_global_method_call( - AudioplayersLinuxPlugin *self, FlMethodCall *method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - int result = 1; - const gchar *method = fl_method_call_get_name(method_call); - FlValue *args = fl_method_call_get_args(method_call); - - if (strcmp(method, "setAudioContext") == 0) { - audioplayers_linux_plugin_on_global_log( - "Setting AudioContext is not supported on Linux"); - } else if (strcmp(method, "emitLog") == 0) { - auto flMessage = fl_value_lookup_string(args, "message"); - auto message = - flMessage == nullptr ? "" : fl_value_get_string(flMessage); - audioplayers_linux_plugin_on_global_log(message); - } else if (strcmp(method, "emitError") == 0) { - auto flCode = fl_value_lookup_string(args, "code"); - auto code = flCode == nullptr ? "" : fl_value_get_string(flCode); - auto flMessage = fl_value_lookup_string(args, "message"); - auto message = - flMessage == nullptr ? "" : fl_value_get_string(flMessage); - fl_event_channel_send_error(globalEvents, code, message, nullptr, - nullptr, nullptr); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - fl_method_call_respond(method_call, response, nullptr); - return; - } - - response = FL_METHOD_RESPONSE( - fl_method_success_response_new(fl_value_new_int(result))); + AudioplayersLinuxPlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + int result = 1; + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (strcmp(method, "setAudioContext") == 0) { + audioplayers_linux_plugin_on_global_log( + "Setting AudioContext is not supported on Linux"); + } else if (strcmp(method, "emitLog") == 0) { + auto flMessage = fl_value_lookup_string(args, "message"); + auto message = flMessage == nullptr ? "" : fl_value_get_string(flMessage); + audioplayers_linux_plugin_on_global_log(message); + } else if (strcmp(method, "emitError") == 0) { + auto flCode = fl_value_lookup_string(args, "code"); + auto code = flCode == nullptr ? "" : fl_value_get_string(flCode); + auto flMessage = fl_value_lookup_string(args, "message"); + auto message = flMessage == nullptr ? "" : fl_value_get_string(flMessage); + fl_event_channel_send_error(globalEvents, code, message, nullptr, nullptr, + nullptr); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); fl_method_call_respond(method_call, response, nullptr); + return; + } + + response = FL_METHOD_RESPONSE( + fl_method_success_response_new(fl_value_new_int(result))); + fl_method_call_respond(method_call, response, nullptr); } static void audioplayers_linux_plugin_handle_method_call( - AudioplayersLinuxPlugin *self, FlMethodCall *method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - const gchar *method = fl_method_call_get_name(method_call); - FlValue *args = fl_method_call_get_args(method_call); - - auto flPlayerId = fl_value_lookup_string(args, "playerId"); - if (flPlayerId == nullptr) { + AudioplayersLinuxPlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + auto flPlayerId = fl_value_lookup_string(args, "playerId"); + if (flPlayerId == nullptr) { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "LinuxAudioError", "Call missing mandatory parameter playerId.", + nullptr)); + fl_method_call_respond(method_call, response, nullptr); + return; + } + auto playerId = std::string(fl_value_get_string(flPlayerId)); + + if (strcmp(method, "create") == 0) { + audioplayers_linux_plugin_create_player(playerId); + response = + FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_int(1))); + fl_method_call_respond(method_call, response, nullptr); + return; + } + + auto player = audioplayers_linux_plugin_get_player(playerId); + if (!player) { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "LinuxAudioError", + "Player has not yet been created or has already been disposed.", + nullptr)); + fl_method_call_respond(method_call, response, nullptr); + return; + } + + FlValue* result = nullptr; + + try { + if (strcmp(method, "pause") == 0) { + player->Pause(); + } else if (strcmp(method, "resume") == 0) { + player->Resume(); + } else if (strcmp(method, "stop") == 0) { + player->Pause(); + player->SetPosition(0); + } else if (strcmp(method, "release") == 0) { + player->ReleaseMediaSource(); + } else if (strcmp(method, "seek") == 0) { + auto flPosition = fl_value_lookup_string(args, "position"); + int position = flPosition == nullptr + ? (int)(player->GetPosition().value_or(0)) + : fl_value_get_int(flPosition); + player->SetPosition(position); + } else if (strcmp(method, "setSourceUrl") == 0) { + auto flUrl = fl_value_lookup_string(args, "url"); + if (flUrl == nullptr) { response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", "Call missing mandatory parameter playerId.", nullptr)); - fl_method_call_respond(method_call, response, nullptr); - return; - } - auto playerId = std::string(fl_value_get_string(flPlayerId)); - - if (strcmp(method, "create") == 0) { - audioplayers_linux_plugin_create_player(playerId); - response = FL_METHOD_RESPONSE( - fl_method_success_response_new(fl_value_new_int(1))); + "LinuxAudioError", "Null URL received on setSourceUrl.", nullptr)); fl_method_call_respond(method_call, response, nullptr); return; - } - - auto player = audioplayers_linux_plugin_get_player(playerId); - if(!player) { + } + auto url = std::string(fl_value_get_string(flUrl)); + + auto flIsLocal = fl_value_lookup_string(args, "isLocal"); + bool isLocal = + flIsLocal == nullptr ? false : fl_value_get_bool(flIsLocal); + if (isLocal) { + url = std::string("file://") + url; + } + player->SetSourceUrl(url); + } else if (strcmp(method, "getDuration") == 0) { + auto optDuration = player->GetDuration(); + result = optDuration.has_value() ? fl_value_new_int(optDuration.value()) + : nullptr; + } else if (strcmp(method, "setVolume") == 0) { + auto flVolume = fl_value_lookup_string(args, "volume"); + double volume = flVolume == nullptr ? 1.0 : fl_value_get_float(flVolume); + player->SetVolume(volume); + } else if (strcmp(method, "getCurrentPosition") == 0) { + auto optPosition = player->GetPosition(); + result = optPosition.has_value() ? fl_value_new_int(optPosition.value()) + : nullptr; + } else if (strcmp(method, "setPlaybackRate") == 0) { + auto flPlaybackRate = fl_value_lookup_string(args, "playbackRate"); + double playbackRate = + flPlaybackRate == nullptr ? 1.0 : fl_value_get_float(flPlaybackRate); + player->SetPlaybackRate(playbackRate); + } else if (strcmp(method, "setReleaseMode") == 0) { + auto flReleaseMode = fl_value_lookup_string(args, "releaseMode"); + std::string releaseMode = + flReleaseMode == nullptr + ? std::string() + : std::string(fl_value_get_string(flReleaseMode)); + if (releaseMode.empty()) { response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", "Player has not yet been created or has already been disposed.", - nullptr)); + "LinuxAudioError", + "Error calling setReleaseMode, releaseMode cannot be null", + nullptr)); fl_method_call_respond(method_call, response, nullptr); return; + } + auto looping = releaseMode.find("loop") != std::string::npos; + player->SetLooping(looping); + } else if (strcmp(method, "setPlayerMode") == 0) { + // TODO check support for low latency mode: + // https://gstreamer.freedesktop.org/documentation/additional/design/latency.html?gi-language=c + } else if (strcmp(method, "setBalance") == 0) { + auto flBalance = fl_value_lookup_string(args, "balance"); + double balance = + flBalance == nullptr ? 0.0f : fl_value_get_float(flBalance); + player->SetBalance(balance); + } else if (strcmp(method, "emitLog") == 0) { + auto flMessage = fl_value_lookup_string(args, "message"); + auto message = flMessage == nullptr ? "" : fl_value_get_string(flMessage); + player->OnLog(message); + } else if (strcmp(method, "emitError") == 0) { + auto flCode = fl_value_lookup_string(args, "code"); + auto code = flCode == nullptr ? "" : fl_value_get_string(flCode); + auto flMessage = fl_value_lookup_string(args, "message"); + auto message = flMessage == nullptr ? "" : fl_value_get_string(flMessage); + player->OnError(code, message, nullptr, nullptr); + } else if (strcmp(method, "dispose") == 0) { + player->Dispose(); + audioPlayers.erase(playerId); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + fl_method_call_respond(method_call, response, nullptr); + return; } - FlValue *result = nullptr; - - try { - if (strcmp(method, "pause") == 0) { - player->Pause(); - } else if (strcmp(method, "resume") == 0) { - player->Resume(); - } else if (strcmp(method, "stop") == 0) { - player->Pause(); - player->SetPosition(0); - } else if (strcmp(method, "release") == 0) { - player->ReleaseMediaSource(); - } else if (strcmp(method, "seek") == 0) { - auto flPosition = fl_value_lookup_string(args, "position"); - int position = flPosition == nullptr ? (int)(player->GetPosition().value_or(0)) - : fl_value_get_int(flPosition); - player->SetPosition(position); - } else if (strcmp(method, "setSourceUrl") == 0) { - auto flUrl = fl_value_lookup_string(args, "url"); - if (flUrl == nullptr) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", "Null URL received on setSourceUrl.", nullptr)); - fl_method_call_respond(method_call, response, nullptr); - return; - } - auto url = std::string(fl_value_get_string(flUrl)); - - auto flIsLocal = fl_value_lookup_string(args, "isLocal"); - bool isLocal = - flIsLocal == nullptr ? false : fl_value_get_bool(flIsLocal); - if (isLocal) { - url = std::string("file://") + url; - } - player->SetSourceUrl(url); - } else if (strcmp(method, "getDuration") == 0) { - auto optDuration = player->GetDuration(); - result = optDuration.has_value() ? fl_value_new_int(optDuration.value()) : nullptr; - } else if (strcmp(method, "setVolume") == 0) { - auto flVolume = fl_value_lookup_string(args, "volume"); - double volume = - flVolume == nullptr ? 1.0 : fl_value_get_float(flVolume); - player->SetVolume(volume); - } else if (strcmp(method, "getCurrentPosition") == 0) { - auto optPosition = player->GetPosition(); - result = optPosition.has_value() ? fl_value_new_int(optPosition.value()) : nullptr; - } else if (strcmp(method, "setPlaybackRate") == 0) { - auto flPlaybackRate = fl_value_lookup_string(args, "playbackRate"); - double playbackRate = flPlaybackRate == nullptr - ? 1.0 - : fl_value_get_float(flPlaybackRate); - player->SetPlaybackRate(playbackRate); - } else if (strcmp(method, "setReleaseMode") == 0) { - auto flReleaseMode = fl_value_lookup_string(args, "releaseMode"); - std::string releaseMode = - flReleaseMode == nullptr - ? std::string() - : std::string(fl_value_get_string(flReleaseMode)); - if (releaseMode.empty()) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", "Error calling setReleaseMode, releaseMode cannot be null", - nullptr)); - fl_method_call_respond(method_call, response, nullptr); - return; - } - auto looping = releaseMode.find("loop") != std::string::npos; - player->SetLooping(looping); - } else if (strcmp(method, "setPlayerMode") == 0) { - // TODO check support for low latency mode: - // https://gstreamer.freedesktop.org/documentation/additional/design/latency.html?gi-language=c - } else if (strcmp(method, "setBalance") == 0) { - auto flBalance = fl_value_lookup_string(args, "balance"); - double balance = - flBalance == nullptr ? 0.0f : fl_value_get_float(flBalance); - player->SetBalance(balance); - } else if (strcmp(method, "emitLog") == 0) { - auto flMessage = fl_value_lookup_string(args, "message"); - auto message = - flMessage == nullptr ? "" : fl_value_get_string(flMessage); - player->OnLog(message); - } else if (strcmp(method, "emitError") == 0) { - auto flCode = fl_value_lookup_string(args, "code"); - auto code = flCode == nullptr ? "" : fl_value_get_string(flCode); - auto flMessage = fl_value_lookup_string(args, "message"); - auto message = - flMessage == nullptr ? "" : fl_value_get_string(flMessage); - player->OnError(code, message, nullptr, nullptr); - } else if (strcmp(method, "dispose") == 0) { - player->Dispose(); - audioPlayers.erase(playerId); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - fl_method_call_respond(method_call, response, nullptr); - return; - } - - response = FL_METHOD_RESPONSE( - fl_method_success_response_new(result)); - fl_method_call_respond(method_call, response, nullptr); - } catch (const gchar* error) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", error, nullptr)); - fl_method_call_respond(method_call, response, nullptr); - } catch (...) { - std::exception_ptr p = std::current_exception(); - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - "LinuxAudioError", p ? p.__cxa_exception_type()->name() : "Unknown AudioPlayersLinux error", nullptr)); - fl_method_call_respond(method_call, response, nullptr); - } + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + fl_method_call_respond(method_call, response, nullptr); + } catch (const gchar* error) { + response = FL_METHOD_RESPONSE( + fl_method_error_response_new("LinuxAudioError", error, nullptr)); + fl_method_call_respond(method_call, response, nullptr); + } catch (...) { + std::exception_ptr p = std::current_exception(); + response = FL_METHOD_RESPONSE( + fl_method_error_response_new("LinuxAudioError", + p ? p.__cxa_exception_type()->name() + : "Unknown AudioPlayersLinux error", + nullptr)); + fl_method_call_respond(method_call, response, nullptr); + } } -static void audioplayers_linux_plugin_dispose(GObject *object) { - for (const auto& entry : audioPlayers) { - entry.second->Dispose(); - } - gst_deinit(); - g_clear_object(&globalEvents); - g_clear_object(&globalMethods); - g_clear_object(&methods); - G_OBJECT_CLASS(audioplayers_linux_plugin_parent_class)->dispose(object); +static void audioplayers_linux_plugin_dispose(GObject* object) { + for (const auto& entry : audioPlayers) { + entry.second->Dispose(); + } + gst_deinit(); + g_clear_object(&globalEvents); + g_clear_object(&globalMethods); + g_clear_object(&methods); + G_OBJECT_CLASS(audioplayers_linux_plugin_parent_class)->dispose(object); } static void audioplayers_linux_plugin_class_init( - AudioplayersLinuxPluginClass *klass) { - G_OBJECT_CLASS(klass)->dispose = audioplayers_linux_plugin_dispose; + AudioplayersLinuxPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = audioplayers_linux_plugin_dispose; } -static void audioplayers_linux_plugin_init(AudioplayersLinuxPlugin *self) {} +static void audioplayers_linux_plugin_init(AudioplayersLinuxPlugin* self) {} -static void method_call_cb(FlMethodChannel *methods, FlMethodCall *method_call, +static void method_call_cb(FlMethodChannel* methods, + FlMethodCall* method_call, gpointer user_data) { - AudioplayersLinuxPlugin *plugin = AUDIOPLAYERS_LINUX_PLUGIN(user_data); - audioplayers_linux_plugin_handle_method_call(plugin, method_call); + AudioplayersLinuxPlugin* plugin = AUDIOPLAYERS_LINUX_PLUGIN(user_data); + audioplayers_linux_plugin_handle_method_call(plugin, method_call); } -static void method_call_global_cb(FlMethodChannel *methods, - FlMethodCall *method_call, +static void method_call_global_cb(FlMethodChannel* methods, + FlMethodCall* method_call, gpointer user_data) { - AudioplayersLinuxPlugin *plugin = AUDIOPLAYERS_LINUX_PLUGIN(user_data); - audioplayers_linux_plugin_handle_global_method_call(plugin, method_call); + AudioplayersLinuxPlugin* plugin = AUDIOPLAYERS_LINUX_PLUGIN(user_data); + audioplayers_linux_plugin_handle_global_method_call(plugin, method_call); } void audioplayers_linux_plugin_register_with_registrar( - FlPluginRegistrar *registrar) { - AudioplayersLinuxPlugin *plugin = AUDIOPLAYERS_LINUX_PLUGIN( - g_object_new(audioplayers_linux_plugin_get_type(), nullptr)); + FlPluginRegistrar* registrar) { + AudioplayersLinuxPlugin* plugin = AUDIOPLAYERS_LINUX_PLUGIN( + g_object_new(audioplayers_linux_plugin_get_type(), nullptr)); - binaryMessenger = fl_plugin_registrar_get_messenger(registrar); + binaryMessenger = fl_plugin_registrar_get_messenger(registrar); - g_autoptr(FlStandardMethodCodec) methodCodec = - fl_standard_method_codec_new(); - methods = fl_method_channel_new(binaryMessenger, "xyz.luan/audioplayers", - FL_METHOD_CODEC(methodCodec)); + g_autoptr(FlStandardMethodCodec) methodCodec = fl_standard_method_codec_new(); + methods = fl_method_channel_new(binaryMessenger, "xyz.luan/audioplayers", + FL_METHOD_CODEC(methodCodec)); - g_autoptr(FlStandardMethodCodec) globalMethodCodec = - fl_standard_method_codec_new(); - globalMethods = - fl_method_channel_new(binaryMessenger, "xyz.luan/audioplayers.global", - FL_METHOD_CODEC(globalMethodCodec)); + g_autoptr(FlStandardMethodCodec) globalMethodCodec = + fl_standard_method_codec_new(); + globalMethods = + fl_method_channel_new(binaryMessenger, "xyz.luan/audioplayers.global", + FL_METHOD_CODEC(globalMethodCodec)); - g_autoptr(FlStandardMethodCodec) globalEventCodec = - fl_standard_method_codec_new(); - globalEvents = fl_event_channel_new(binaryMessenger, - "xyz.luan/audioplayers.global/events", - FL_METHOD_CODEC(globalEventCodec)); + g_autoptr(FlStandardMethodCodec) globalEventCodec = + fl_standard_method_codec_new(); + globalEvents = fl_event_channel_new(binaryMessenger, + "xyz.luan/audioplayers.global/events", + FL_METHOD_CODEC(globalEventCodec)); - fl_method_channel_set_method_call_handler( - methods, method_call_cb, g_object_ref(plugin), g_object_unref); + fl_method_channel_set_method_call_handler( + methods, method_call_cb, g_object_ref(plugin), g_object_unref); - fl_method_channel_set_method_call_handler( - globalMethods, method_call_global_cb, g_object_ref(plugin), - g_object_unref); + fl_method_channel_set_method_call_handler( + globalMethods, method_call_global_cb, g_object_ref(plugin), + g_object_unref); - // No need to set handler for `globalEvents` as no events are received. + // No need to set handler for `globalEvents` as no events are received. - g_object_unref(plugin); + g_object_unref(plugin); } diff --git a/packages/audioplayers_linux/linux/include/audioplayers_linux/audioplayers_linux_plugin.h b/packages/audioplayers_linux/linux/include/audioplayers_linux/audioplayers_linux_plugin.h index 8bf491491..aa6a7b9a9 100644 --- a/packages/audioplayers_linux/linux/include/audioplayers_linux/audioplayers_linux_plugin.h +++ b/packages/audioplayers_linux/linux/include/audioplayers_linux/audioplayers_linux_plugin.h @@ -13,13 +13,13 @@ G_BEGIN_DECLS typedef struct _AudioplayersLinuxPlugin AudioplayersLinuxPlugin; typedef struct { - GObjectClass parent_class; + GObjectClass parent_class; } AudioplayersLinuxPluginClass; FLUTTER_PLUGIN_EXPORT GType audioplayers_linux_plugin_get_type(); FLUTTER_PLUGIN_EXPORT void audioplayers_linux_plugin_register_with_registrar( - FlPluginRegistrar *registrar); + FlPluginRegistrar* registrar); G_END_DECLS diff --git a/packages/audioplayers_windows/windows/MediaEngineExtension.cpp b/packages/audioplayers_windows/windows/MediaEngineExtension.cpp index d0131cd91..6b2de4704 100644 --- a/packages/audioplayers_windows/windows/MediaEngineExtension.cpp +++ b/packages/audioplayers_windows/windows/MediaEngineExtension.cpp @@ -14,96 +14,91 @@ #include #include +#include #include "MediaEngineWrapper.h" #include "MediaFoundationHelpers.h" -#include #include "MediaEngineExtension.h" using namespace Microsoft::WRL; -namespace media -{ +namespace media { -IFACEMETHODIMP MediaEngineExtension::CanPlayType(BOOL /*isAudioOnly*/, BSTR /*mimeType*/, MF_MEDIA_ENGINE_CANPLAY* result) noexcept -{ - *result = MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; - return S_OK; +IFACEMETHODIMP MediaEngineExtension::CanPlayType( + BOOL /*isAudioOnly*/, + BSTR /*mimeType*/, + MF_MEDIA_ENGINE_CANPLAY* result) noexcept { + *result = MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; + return S_OK; } -IFACEMETHODIMP MediaEngineExtension::BeginCreateObject(BSTR /*url*/, IMFByteStream* /*byteStream*/, MF_OBJECT_TYPE type, IUnknown** cancelCookie, - IMFAsyncCallback* callback, IUnknown* state) noexcept -try -{ - if(cancelCookie) - { - *cancelCookie = nullptr; - } - winrt::com_ptr localSource; - { - auto lock = m_lock.lock(); - THROW_HR_IF(MF_E_SHUTDOWN, m_hasShutdown); - localSource = m_mfMediaSource; - } - - if(type == MF_OBJECT_MEDIASOURCE && localSource != nullptr) - { - winrt::com_ptr asyncResult; - THROW_IF_FAILED(MFCreateAsyncResult(localSource.get(), callback, state, asyncResult.put())); - THROW_IF_FAILED(asyncResult->SetStatus(S_OK)); - m_uriType = ExtensionUriType::CustomSource; - // Invoke the callback synchronously since no outstanding work is required. - THROW_IF_FAILED(callback->Invoke(asyncResult.get())); - } - else - { - THROW_HR(MF_E_UNEXPECTED); - } - - return S_OK; +IFACEMETHODIMP MediaEngineExtension::BeginCreateObject( + BSTR /*url*/, + IMFByteStream* /*byteStream*/, + MF_OBJECT_TYPE type, + IUnknown** cancelCookie, + IMFAsyncCallback* callback, + IUnknown* state) noexcept try { + if (cancelCookie) { + *cancelCookie = nullptr; + } + winrt::com_ptr localSource; + { + auto lock = m_lock.lock(); + THROW_HR_IF(MF_E_SHUTDOWN, m_hasShutdown); + localSource = m_mfMediaSource; + } + + if (type == MF_OBJECT_MEDIASOURCE && localSource != nullptr) { + winrt::com_ptr asyncResult; + THROW_IF_FAILED(MFCreateAsyncResult(localSource.get(), callback, state, + asyncResult.put())); + THROW_IF_FAILED(asyncResult->SetStatus(S_OK)); + m_uriType = ExtensionUriType::CustomSource; + // Invoke the callback synchronously since no outstanding work is required. + THROW_IF_FAILED(callback->Invoke(asyncResult.get())); + } else { + THROW_HR(MF_E_UNEXPECTED); + } + + return S_OK; } CATCH_RETURN(); -STDMETHODIMP MediaEngineExtension::CancelObjectCreation(_In_ IUnknown* /*cancelCookie*/) noexcept -{ - // Cancellation not supported - return E_NOTIMPL; +STDMETHODIMP MediaEngineExtension::CancelObjectCreation( + _In_ IUnknown* /*cancelCookie*/) noexcept { + // Cancellation not supported + return E_NOTIMPL; } -STDMETHODIMP MediaEngineExtension::EndCreateObject(IMFAsyncResult* result, IUnknown** object) noexcept -try -{ - *object = nullptr; - if(m_uriType == ExtensionUriType::CustomSource) - { - THROW_IF_FAILED(result->GetStatus()); - THROW_IF_FAILED(result->GetObject(object)); - m_uriType = ExtensionUriType::Unknown; - } - else - { - THROW_HR(MF_E_UNEXPECTED); - } - return S_OK; +STDMETHODIMP MediaEngineExtension::EndCreateObject(IMFAsyncResult* result, + IUnknown** object) noexcept + try { + *object = nullptr; + if (m_uriType == ExtensionUriType::CustomSource) { + THROW_IF_FAILED(result->GetStatus()); + THROW_IF_FAILED(result->GetObject(object)); + m_uriType = ExtensionUriType::Unknown; + } else { + THROW_HR(MF_E_UNEXPECTED); + } + return S_OK; } CATCH_RETURN(); -void MediaEngineExtension::SetMediaSource(IUnknown* mfMediaSource) -{ - auto lock = m_lock.lock(); - THROW_HR_IF(MF_E_SHUTDOWN, m_hasShutdown); - m_mfMediaSource.copy_from(mfMediaSource); +void MediaEngineExtension::SetMediaSource(IUnknown* mfMediaSource) { + auto lock = m_lock.lock(); + THROW_HR_IF(MF_E_SHUTDOWN, m_hasShutdown); + m_mfMediaSource.copy_from(mfMediaSource); } // Break circular references. -void MediaEngineExtension::Shutdown() -{ - auto lock = m_lock.lock(); - if(!m_hasShutdown) - { - m_mfMediaSource = nullptr; - m_hasShutdown = true; - } +void MediaEngineExtension::Shutdown() { + auto lock = m_lock.lock(); + if (!m_hasShutdown) { + m_mfMediaSource = nullptr; + m_hasShutdown = true; + } } -} // namespace media +} // namespace media diff --git a/packages/audioplayers_windows/windows/MediaEngineExtension.h b/packages/audioplayers_windows/windows/MediaEngineExtension.h index 2400d593c..551daabb4 100644 --- a/packages/audioplayers_windows/windows/MediaEngineExtension.h +++ b/packages/audioplayers_windows/windows/MediaEngineExtension.h @@ -1,41 +1,41 @@ #pragma once -namespace media -{ +namespace media { // This implementation of IMFMediaEngineExtension is used to integrate a custom // IMFMediaSource with the MediaEngine pipeline class MediaEngineExtension - : public winrt::implements -{ - public: - MediaEngineExtension() = default; - ~MediaEngineExtension() override = default; + : public winrt::implements { + public: + MediaEngineExtension() = default; + ~MediaEngineExtension() override = default; - // IMFMediaEngineExtension - IFACEMETHOD(CanPlayType) - (BOOL isAudioOnly, BSTR mimeType, MF_MEDIA_ENGINE_CANPLAY* result) noexcept override; - IFACEMETHOD(BeginCreateObject) - (BSTR url, IMFByteStream* byteStream, MF_OBJECT_TYPE type, IUnknown** cancelCookie, IMFAsyncCallback* callback, - IUnknown* state) noexcept override; - IFACEMETHOD(CancelObjectCreation)(IUnknown* cancelCookie) noexcept override; - IFACEMETHOD(EndCreateObject) - (IMFAsyncResult* result, IUnknown** object) noexcept override; + // IMFMediaEngineExtension + IFACEMETHOD(CanPlayType) + (BOOL isAudioOnly, + BSTR mimeType, + MF_MEDIA_ENGINE_CANPLAY* result) noexcept override; + IFACEMETHOD(BeginCreateObject) + (BSTR url, + IMFByteStream* byteStream, + MF_OBJECT_TYPE type, + IUnknown** cancelCookie, + IMFAsyncCallback* callback, + IUnknown* state) noexcept override; + IFACEMETHOD(CancelObjectCreation)(IUnknown* cancelCookie) noexcept override; + IFACEMETHOD(EndCreateObject) + (IMFAsyncResult* result, IUnknown** object) noexcept override; - // Public methods - void SetMediaSource(IUnknown* mfMediaSource); - void Shutdown(); + // Public methods + void SetMediaSource(IUnknown* mfMediaSource); + void Shutdown(); - private: - wil::critical_section m_lock; - enum class ExtensionUriType - { - Unknown = 0, - CustomSource - }; - ExtensionUriType m_uriType = ExtensionUriType::Unknown; - bool m_hasShutdown = false; - winrt::com_ptr m_mfMediaSource; + private: + wil::critical_section m_lock; + enum class ExtensionUriType { Unknown = 0, CustomSource }; + ExtensionUriType m_uriType = ExtensionUriType::Unknown; + bool m_hasShutdown = false; + winrt::com_ptr m_mfMediaSource; }; -} // namespace media \ No newline at end of file +} // namespace media \ No newline at end of file diff --git a/packages/audioplayers_windows/windows/MediaEngineWrapper.cpp b/packages/audioplayers_windows/windows/MediaEngineWrapper.cpp index eceed4f74..ffe071f37 100644 --- a/packages/audioplayers_windows/windows/MediaEngineWrapper.cpp +++ b/packages/audioplayers_windows/windows/MediaEngineWrapper.cpp @@ -16,382 +16,360 @@ #include #include +#include #include "MediaEngineWrapper.h" #include "MediaFoundationHelpers.h" #include "audioplayers_helpers.h" -#include using namespace Microsoft::WRL; -namespace media -{ - -namespace -{ - class MediaEngineCallbackHelper - : public winrt::implements - { - public: - MediaEngineCallbackHelper(std::function onLoadedCB, MediaEngineWrapper::ErrorCB errorCB, - MediaEngineWrapper::BufferingStateChangeCB bufferingStateChangeCB, std::function playbackEndedCB, - std::function timeUpdateCB, std::function seekCompletedCB) - : m_onLoadedCB(onLoadedCB), m_errorCB(errorCB), m_bufferingStateChangeCB(bufferingStateChangeCB), - m_playbackEndedCB(playbackEndedCB), m_timeUpdateCB(timeUpdateCB), m_seekCompletedCB(seekCompletedCB) - { - // Ensure that callbacks are valid - THROW_HR_IF(E_INVALIDARG, !m_onLoadedCB); - THROW_HR_IF(E_INVALIDARG, !m_errorCB); - THROW_HR_IF(E_INVALIDARG, !m_bufferingStateChangeCB); - THROW_HR_IF(E_INVALIDARG, !m_playbackEndedCB); - THROW_HR_IF(E_INVALIDARG, !m_timeUpdateCB); - THROW_HR_IF(E_INVALIDARG, !m_seekCompletedCB); - } - virtual ~MediaEngineCallbackHelper() = default; - - void DetachParent() - { - auto lock = m_lock.lock(); - m_detached = true; - m_onLoadedCB = nullptr; - m_errorCB = nullptr; - m_bufferingStateChangeCB = nullptr; - m_playbackEndedCB = nullptr; - m_timeUpdateCB = nullptr; - m_seekCompletedCB = nullptr; - } - - // IMFMediaEngineNotify - IFACEMETHODIMP EventNotify(DWORD eventCode, DWORD_PTR param1, DWORD param2) noexcept override - try - { - auto lock = m_lock.lock(); - THROW_HR_IF(MF_E_SHUTDOWN, m_detached); - - switch((MF_MEDIA_ENGINE_EVENT)eventCode) - { - case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: - m_onLoadedCB(); - break; - case MF_MEDIA_ENGINE_EVENT_ERROR: - m_errorCB((MF_MEDIA_ENGINE_ERR)param1, (HRESULT)param2); - break; - case MF_MEDIA_ENGINE_EVENT_CANPLAY: - m_bufferingStateChangeCB(MediaEngineWrapper::BufferingState::HAVE_ENOUGH); - break; - case MF_MEDIA_ENGINE_EVENT_WAITING: - m_bufferingStateChangeCB(MediaEngineWrapper::BufferingState::HAVE_NOTHING); - break; - case MF_MEDIA_ENGINE_EVENT_ENDED: - m_playbackEndedCB(); - break; - case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: - m_timeUpdateCB(); - break; - case MF_MEDIA_ENGINE_EVENT_SEEKED: - m_seekCompletedCB(); - break; - default: - break; - } - - return S_OK; - } - CATCH_RETURN(); - - private: - wil::critical_section m_lock; - std::function m_onLoadedCB; - MediaEngineWrapper::ErrorCB m_errorCB; - MediaEngineWrapper::BufferingStateChangeCB m_bufferingStateChangeCB; - std::function m_playbackEndedCB; - std::function m_timeUpdateCB; - std::function m_seekCompletedCB; - bool m_detached = false; - }; -} // namespace +namespace media { + +namespace { +class MediaEngineCallbackHelper + : public winrt::implements { + public: + MediaEngineCallbackHelper( + std::function onLoadedCB, + MediaEngineWrapper::ErrorCB errorCB, + MediaEngineWrapper::BufferingStateChangeCB bufferingStateChangeCB, + std::function playbackEndedCB, + std::function timeUpdateCB, + std::function seekCompletedCB) + : m_onLoadedCB(onLoadedCB), + m_errorCB(errorCB), + m_bufferingStateChangeCB(bufferingStateChangeCB), + m_playbackEndedCB(playbackEndedCB), + m_timeUpdateCB(timeUpdateCB), + m_seekCompletedCB(seekCompletedCB) { + // Ensure that callbacks are valid + THROW_HR_IF(E_INVALIDARG, !m_onLoadedCB); + THROW_HR_IF(E_INVALIDARG, !m_errorCB); + THROW_HR_IF(E_INVALIDARG, !m_bufferingStateChangeCB); + THROW_HR_IF(E_INVALIDARG, !m_playbackEndedCB); + THROW_HR_IF(E_INVALIDARG, !m_timeUpdateCB); + THROW_HR_IF(E_INVALIDARG, !m_seekCompletedCB); + } + virtual ~MediaEngineCallbackHelper() = default; + + void DetachParent() { + auto lock = m_lock.lock(); + m_detached = true; + m_onLoadedCB = nullptr; + m_errorCB = nullptr; + m_bufferingStateChangeCB = nullptr; + m_playbackEndedCB = nullptr; + m_timeUpdateCB = nullptr; + m_seekCompletedCB = nullptr; + } + + // IMFMediaEngineNotify + IFACEMETHODIMP EventNotify(DWORD eventCode, + DWORD_PTR param1, + DWORD param2) noexcept override try { + auto lock = m_lock.lock(); + THROW_HR_IF(MF_E_SHUTDOWN, m_detached); + + switch ((MF_MEDIA_ENGINE_EVENT)eventCode) { + case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: + m_onLoadedCB(); + break; + case MF_MEDIA_ENGINE_EVENT_ERROR: + m_errorCB((MF_MEDIA_ENGINE_ERR)param1, (HRESULT)param2); + break; + case MF_MEDIA_ENGINE_EVENT_CANPLAY: + m_bufferingStateChangeCB( + MediaEngineWrapper::BufferingState::HAVE_ENOUGH); + break; + case MF_MEDIA_ENGINE_EVENT_WAITING: + m_bufferingStateChangeCB( + MediaEngineWrapper::BufferingState::HAVE_NOTHING); + break; + case MF_MEDIA_ENGINE_EVENT_ENDED: + m_playbackEndedCB(); + break; + case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: + m_timeUpdateCB(); + break; + case MF_MEDIA_ENGINE_EVENT_SEEKED: + m_seekCompletedCB(); + break; + default: + break; + } + + return S_OK; + } + CATCH_RETURN(); + + private: + wil::critical_section m_lock; + std::function m_onLoadedCB; + MediaEngineWrapper::ErrorCB m_errorCB; + MediaEngineWrapper::BufferingStateChangeCB m_bufferingStateChangeCB; + std::function m_playbackEndedCB; + std::function m_timeUpdateCB; + std::function m_seekCompletedCB; + bool m_detached = false; +}; +} // namespace // Public methods -void MediaEngineWrapper::Initialize() -{ - RunSyncInMTA([&]() - { - CreateMediaEngine(); - }); +void MediaEngineWrapper::Initialize() { + RunSyncInMTA([&]() { CreateMediaEngine(); }); } -void MediaEngineWrapper::Pause() -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->Pause()); - }); +void MediaEngineWrapper::Pause() { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->Pause()); + }); } -void MediaEngineWrapper::Shutdown() -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->Shutdown()); - }); +void MediaEngineWrapper::Shutdown() { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->Shutdown()); + }); } -void MediaEngineWrapper::StartPlayingFrom(double timestampInSeconds) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->SetCurrentTime(timestampInSeconds)); - THROW_IF_FAILED(m_mediaEngine->Play()); - }); +void MediaEngineWrapper::StartPlayingFrom(double timestampInSeconds) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->SetCurrentTime(timestampInSeconds)); + THROW_IF_FAILED(m_mediaEngine->Play()); + }); } -void MediaEngineWrapper::Resume() -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->Play()); - }); +void MediaEngineWrapper::Resume() { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->Play()); + }); } -void MediaEngineWrapper::SetBalance(double balance) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - - winrt::com_ptr mediaEngineEx = m_mediaEngine.as(); - THROW_IF_FAILED(mediaEngineEx->SetBalance(balance)); - }); +void MediaEngineWrapper::SetBalance(double balance) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + + winrt::com_ptr mediaEngineEx = + m_mediaEngine.as(); + THROW_IF_FAILED(mediaEngineEx->SetBalance(balance)); + }); } -void MediaEngineWrapper::SetPlaybackRate(double playbackRate) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->SetPlaybackRate(playbackRate)); - }); +void MediaEngineWrapper::SetPlaybackRate(double playbackRate) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->SetPlaybackRate(playbackRate)); + }); } -void MediaEngineWrapper::SetVolume(float volume) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->SetVolume(volume)); - }); +void MediaEngineWrapper::SetVolume(float volume) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->SetVolume(volume)); + }); } -void MediaEngineWrapper::SetLooping(bool isLooping) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->SetLoop(isLooping)); - }); +void MediaEngineWrapper::SetLooping(bool isLooping) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->SetLoop(isLooping)); + }); } -bool MediaEngineWrapper::GetLooping() -{ - bool looping = false; - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - looping = m_mediaEngine->GetLoop(); - }); - return looping; +bool MediaEngineWrapper::GetLooping() { + bool looping = false; + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + looping = m_mediaEngine->GetLoop(); + }); + return looping; } -void MediaEngineWrapper::SeekTo(double timestampInSeconds) -{ - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - THROW_IF_FAILED(m_mediaEngine->SetCurrentTime(timestampInSeconds)); - }); +void MediaEngineWrapper::SeekTo(double timestampInSeconds) { + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + THROW_IF_FAILED(m_mediaEngine->SetCurrentTime(timestampInSeconds)); + }); } // Get media time in seconds, returns NaN if no duration is available. -double MediaEngineWrapper::GetMediaTime() -{ - double currentTimeInSeconds = std::numeric_limits::quiet_NaN(); - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - currentTimeInSeconds = m_mediaEngine->GetCurrentTime(); - }); - return currentTimeInSeconds; +double MediaEngineWrapper::GetMediaTime() { + double currentTimeInSeconds = std::numeric_limits::quiet_NaN(); + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + currentTimeInSeconds = m_mediaEngine->GetCurrentTime(); + }); + return currentTimeInSeconds; } // Get duration in seconds, returns NaN if no duration is available. -double MediaEngineWrapper::GetDuration() -{ - double durationInSeconds = std::numeric_limits::quiet_NaN(); - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - if (m_mediaEngine == nullptr) { - return; - } - durationInSeconds = m_mediaEngine->GetDuration(); - }); - return durationInSeconds; +double MediaEngineWrapper::GetDuration() { + double durationInSeconds = std::numeric_limits::quiet_NaN(); + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + if (m_mediaEngine == nullptr) { + return; + } + durationInSeconds = m_mediaEngine->GetDuration(); + }); + return durationInSeconds; } // Get buffered ranges in milliseconds -std::vector> MediaEngineWrapper::GetBufferedRanges() -{ - std::vector> result; - RunSyncInMTA([&]() - { - auto lock = m_lock.lock(); - - if (m_mediaEngine == nullptr) { - return; - } - - winrt::com_ptr mediaTimeRange; - THROW_IF_FAILED(m_mediaEngine->GetBuffered(mediaTimeRange.put())); - - double start; - double end; - for (uint32_t i=0;iGetLength();i++) - { - mediaTimeRange->GetStart(i, &start); - mediaTimeRange->GetEnd(i, &end); - result.push_back(std::make_tuple(ConvertSecondsToMs(start), ConvertSecondsToMs(end))); - } - }); - return result; +std::vector> +MediaEngineWrapper::GetBufferedRanges() { + std::vector> result; + RunSyncInMTA([&]() { + auto lock = m_lock.lock(); + + if (m_mediaEngine == nullptr) { + return; + } + + winrt::com_ptr mediaTimeRange; + THROW_IF_FAILED(m_mediaEngine->GetBuffered(mediaTimeRange.put())); + + double start; + double end; + for (uint32_t i = 0; i < mediaTimeRange->GetLength(); i++) { + mediaTimeRange->GetStart(i, &start); + mediaTimeRange->GetEnd(i, &end); + result.push_back( + std::make_tuple(ConvertSecondsToMs(start), ConvertSecondsToMs(end))); + } + }); + return result; } // Internal methods void MediaEngineWrapper::CreateMediaEngine() { - winrt::com_ptr classFactory; - winrt::com_ptr creationAttributes; - - m_platformRef.Startup(); - - THROW_IF_FAILED(MFCreateAttributes(creationAttributes.put(), 7)); - m_callbackHelper = winrt::make([&]() { this->OnLoaded(); }, - [&](MF_MEDIA_ENGINE_ERR error, HRESULT hr) { this->OnError(error, hr); }, - [&](BufferingState state) { this->OnBufferingStateChange(state); }, - [&]() { this->OnPlaybackEnded(); }, [&]() { this->OnTimeUpdate(); }, - [&]() { this->OnSeekCompleted(); }); - THROW_IF_FAILED(creationAttributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, m_callbackHelper.get())); - THROW_IF_FAILED(creationAttributes->SetUINT32(MF_MEDIA_ENGINE_CONTENT_PROTECTION_FLAGS, MF_MEDIA_ENGINE_ENABLE_PROTECTED_CONTENT)); - THROW_IF_FAILED(creationAttributes->SetGUID(MF_MEDIA_ENGINE_BROWSER_COMPATIBILITY_MODE, MF_MEDIA_ENGINE_BROWSER_COMPATIBILITY_MODE_IE_EDGE)); - THROW_IF_FAILED(creationAttributes->SetUINT32(MF_MEDIA_ENGINE_AUDIO_CATEGORY, AudioCategory_Media)); - - m_mediaEngineExtension = winrt::make_self(); - THROW_IF_FAILED(creationAttributes->SetUnknown(MF_MEDIA_ENGINE_EXTENSION, m_mediaEngineExtension.get())); - - THROW_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(classFactory.put()))); - m_mediaEngine = nullptr; - THROW_IF_FAILED(classFactory->CreateInstance(0, creationAttributes.get(), m_mediaEngine.put())); + winrt::com_ptr classFactory; + winrt::com_ptr creationAttributes; + + m_platformRef.Startup(); + + THROW_IF_FAILED(MFCreateAttributes(creationAttributes.put(), 7)); + m_callbackHelper = winrt::make( + [&]() { this->OnLoaded(); }, + [&](MF_MEDIA_ENGINE_ERR error, HRESULT hr) { this->OnError(error, hr); }, + [&](BufferingState state) { this->OnBufferingStateChange(state); }, + [&]() { this->OnPlaybackEnded(); }, [&]() { this->OnTimeUpdate(); }, + [&]() { this->OnSeekCompleted(); }); + THROW_IF_FAILED(creationAttributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, + m_callbackHelper.get())); + THROW_IF_FAILED( + creationAttributes->SetUINT32(MF_MEDIA_ENGINE_CONTENT_PROTECTION_FLAGS, + MF_MEDIA_ENGINE_ENABLE_PROTECTED_CONTENT)); + THROW_IF_FAILED(creationAttributes->SetGUID( + MF_MEDIA_ENGINE_BROWSER_COMPATIBILITY_MODE, + MF_MEDIA_ENGINE_BROWSER_COMPATIBILITY_MODE_IE_EDGE)); + THROW_IF_FAILED(creationAttributes->SetUINT32(MF_MEDIA_ENGINE_AUDIO_CATEGORY, + AudioCategory_Media)); + + m_mediaEngineExtension = winrt::make_self(); + THROW_IF_FAILED(creationAttributes->SetUnknown(MF_MEDIA_ENGINE_EXTENSION, + m_mediaEngineExtension.get())); + + THROW_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(classFactory.put()))); + m_mediaEngine = nullptr; + THROW_IF_FAILED(classFactory->CreateInstance(0, creationAttributes.get(), + m_mediaEngine.put())); } void MediaEngineWrapper::SetMediaSource(IMFMediaSource* mediaSource) { - winrt::com_ptr sourceUnknown; - THROW_IF_FAILED(mediaSource->QueryInterface(IID_PPV_ARGS(sourceUnknown.put()))); - m_mediaEngineExtension->SetMediaSource(sourceUnknown.get()); - - winrt::com_ptr mediaEngineEx = m_mediaEngine.as(); - wil::unique_bstr source = wil::make_bstr(L"customSrc"); - THROW_IF_FAILED(mediaEngineEx->SetSource(source.get())); + winrt::com_ptr sourceUnknown; + THROW_IF_FAILED( + mediaSource->QueryInterface(IID_PPV_ARGS(sourceUnknown.put()))); + m_mediaEngineExtension->SetMediaSource(sourceUnknown.get()); + + winrt::com_ptr mediaEngineEx = + m_mediaEngine.as(); + wil::unique_bstr source = wil::make_bstr(L"customSrc"); + THROW_IF_FAILED(mediaEngineEx->SetSource(source.get())); } void MediaEngineWrapper::ReleaseMediaSource() { - m_mediaEngineExtension->SetMediaSource(nullptr); - m_mediaEngine->SetSource(nullptr); + m_mediaEngineExtension->SetMediaSource(nullptr); + m_mediaEngine->SetSource(nullptr); } // Callback methods -void MediaEngineWrapper::OnLoaded() -{ - if(m_initializedCB) - { - m_initializedCB(); - } +void MediaEngineWrapper::OnLoaded() { + if (m_initializedCB) { + m_initializedCB(); + } } -void MediaEngineWrapper::OnError(MF_MEDIA_ENGINE_ERR error, HRESULT hr) -{ - if(m_errorCB) - { - m_errorCB(error, hr); - } +void MediaEngineWrapper::OnError(MF_MEDIA_ENGINE_ERR error, HRESULT hr) { + if (m_errorCB) { + m_errorCB(error, hr); + } } -void MediaEngineWrapper::OnBufferingStateChange(BufferingState state) -{ - if(m_bufferingStateChangeCB) - { - m_bufferingStateChangeCB(state); - } +void MediaEngineWrapper::OnBufferingStateChange(BufferingState state) { + if (m_bufferingStateChangeCB) { + m_bufferingStateChangeCB(state); + } } -void MediaEngineWrapper::OnPlaybackEnded() -{ - if(m_playbackEndedCB) - { - m_playbackEndedCB(); - } +void MediaEngineWrapper::OnPlaybackEnded() { + if (m_playbackEndedCB) { + m_playbackEndedCB(); + } } -void MediaEngineWrapper::OnTimeUpdate() -{ - if(m_timeUpdateCB) - { - m_timeUpdateCB(); - } +void MediaEngineWrapper::OnTimeUpdate() { + if (m_timeUpdateCB) { + m_timeUpdateCB(); + } } -void MediaEngineWrapper::OnSeekCompleted() -{ - if(m_seekCompletedCB) - { - m_seekCompletedCB(); - } +void MediaEngineWrapper::OnSeekCompleted() { + if (m_seekCompletedCB) { + m_seekCompletedCB(); + } } -} // namespace media +} // namespace media diff --git a/packages/audioplayers_windows/windows/MediaEngineWrapper.h b/packages/audioplayers_windows/windows/MediaEngineWrapper.h index 7a5bf0228..4399935a9 100644 --- a/packages/audioplayers_windows/windows/MediaEngineWrapper.h +++ b/packages/audioplayers_windows/windows/MediaEngineWrapper.h @@ -2,85 +2,86 @@ #include -#include "MediaFoundationHelpers.h" #include "MediaEngineExtension.h" +#include "MediaFoundationHelpers.h" -namespace media -{ +namespace media { // This class handles creation and management of the MediaFoundation // MediaEngine. // - It uses the provided IMFMediaSource to feed media samples into the // MediaEngine pipeline. -class MediaEngineWrapper : public winrt::implements -{ - public: - using ErrorCB = std::function; - - enum class BufferingState - { - HAVE_NOTHING = 0, - HAVE_ENOUGH = 1 - }; - using BufferingStateChangeCB = std::function; - - MediaEngineWrapper(std::function initializedCB, ErrorCB errorCB, BufferingStateChangeCB bufferingStateChangeCB, - std::function playbackEndedCB, std::function timeUpdateCB, std::function seekCompletedCB) - : m_initializedCB(initializedCB), m_errorCB(errorCB), m_bufferingStateChangeCB(bufferingStateChangeCB), - m_playbackEndedCB(playbackEndedCB), m_timeUpdateCB(timeUpdateCB), m_seekCompletedCB(seekCompletedCB) - { - } - ~MediaEngineWrapper() {} - - // Create the media engine - void Initialize(); - - // Initialize with the provided media source - void SetMediaSource(IMFMediaSource* mediaSource); - - // Release media resources - void ReleaseMediaSource(); - - // Stop playback and cleanup resources - void Pause(); - void Shutdown(); - - // Control various aspects of playback - void StartPlayingFrom(double timestampInSeconds); - void Resume(); - void SetPlaybackRate(double playbackRate); - void SetVolume(float volume); - void SetBalance(double balance); - void SetLooping(bool isLooping); - void SeekTo(double timeStamp); - - // Query the current playback position - double GetMediaTime(); - double GetDuration(); - - bool GetLooping(); - - std::vector> GetBufferedRanges(); - - private: - wil::critical_section m_lock; - std::function m_initializedCB; - ErrorCB m_errorCB; - BufferingStateChangeCB m_bufferingStateChangeCB; - std::function m_playbackEndedCB; - std::function m_timeUpdateCB; - std::function m_seekCompletedCB; - MFPlatformRef m_platformRef; - winrt::com_ptr m_mediaEngine; - winrt::com_ptr m_mediaEngineExtension; - winrt::com_ptr m_callbackHelper; - void CreateMediaEngine(); - void OnLoaded(); - void OnError(MF_MEDIA_ENGINE_ERR error, HRESULT hr); - void OnBufferingStateChange(BufferingState state); - void OnPlaybackEnded(); - void OnTimeUpdate(); - void OnSeekCompleted(); +class MediaEngineWrapper + : public winrt::implements { + public: + using ErrorCB = std::function; + + enum class BufferingState { HAVE_NOTHING = 0, HAVE_ENOUGH = 1 }; + using BufferingStateChangeCB = std::function; + + MediaEngineWrapper(std::function initializedCB, + ErrorCB errorCB, + BufferingStateChangeCB bufferingStateChangeCB, + std::function playbackEndedCB, + std::function timeUpdateCB, + std::function seekCompletedCB) + : m_initializedCB(initializedCB), + m_errorCB(errorCB), + m_bufferingStateChangeCB(bufferingStateChangeCB), + m_playbackEndedCB(playbackEndedCB), + m_timeUpdateCB(timeUpdateCB), + m_seekCompletedCB(seekCompletedCB) {} + ~MediaEngineWrapper() {} + + // Create the media engine + void Initialize(); + + // Initialize with the provided media source + void SetMediaSource(IMFMediaSource* mediaSource); + + // Release media resources + void ReleaseMediaSource(); + + // Stop playback and cleanup resources + void Pause(); + void Shutdown(); + + // Control various aspects of playback + void StartPlayingFrom(double timestampInSeconds); + void Resume(); + void SetPlaybackRate(double playbackRate); + void SetVolume(float volume); + void SetBalance(double balance); + void SetLooping(bool isLooping); + void SeekTo(double timeStamp); + + // Query the current playback position + double GetMediaTime(); + double GetDuration(); + + bool GetLooping(); + + std::vector> GetBufferedRanges(); + + private: + wil::critical_section m_lock; + std::function m_initializedCB; + ErrorCB m_errorCB; + BufferingStateChangeCB m_bufferingStateChangeCB; + std::function m_playbackEndedCB; + std::function m_timeUpdateCB; + std::function m_seekCompletedCB; + MFPlatformRef m_platformRef; + winrt::com_ptr m_mediaEngine; + winrt::com_ptr m_mediaEngineExtension; + winrt::com_ptr m_callbackHelper; + void CreateMediaEngine(); + void OnLoaded(); + void OnError(MF_MEDIA_ENGINE_ERR error, HRESULT hr); + void OnBufferingStateChange(BufferingState state); + void OnPlaybackEnded(); + void OnTimeUpdate(); + void OnSeekCompleted(); }; -} // namespace media +} // namespace media diff --git a/packages/audioplayers_windows/windows/MediaFoundationHelpers.h b/packages/audioplayers_windows/windows/MediaFoundationHelpers.h index 333cce8e7..15fbfb995 100644 --- a/packages/audioplayers_windows/windows/MediaFoundationHelpers.h +++ b/packages/audioplayers_windows/windows/MediaFoundationHelpers.h @@ -1,146 +1,131 @@ #pragma once -namespace media -{ - -class MFPlatformRef -{ - public: - MFPlatformRef() {} - - virtual ~MFPlatformRef() { Shutdown(); } - - void Startup() - { - if(!m_started) - { - THROW_IF_FAILED(MFStartup(MF_VERSION, MFSTARTUP_FULL)); - m_started = true; - } - } - - void Shutdown() - { - if(m_started) - { - THROW_IF_FAILED(MFShutdown()); - m_started = false; - } - } +namespace media { - private: - bool m_started = false; -}; +class MFPlatformRef { + public: + MFPlatformRef() {} -class MFCallbackBase : public winrt::implements -{ - public: - MFCallbackBase(DWORD flags = 0, DWORD queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED) : m_flags(flags), m_queue(queue) {} + virtual ~MFPlatformRef() { Shutdown(); } - DWORD GetQueue() const { return m_queue; } - DWORD GetFlags() const { return m_flags; } + void Startup() { + if (!m_started) { + THROW_IF_FAILED(MFStartup(MF_VERSION, MFSTARTUP_FULL)); + m_started = true; + } + } - // IMFAsyncCallback methods - IFACEMETHODIMP GetParameters(_Out_ DWORD* flags, _Out_ DWORD* queue) - { - *flags = m_flags; - *queue = m_queue; - return S_OK; + void Shutdown() { + if (m_started) { + THROW_IF_FAILED(MFShutdown()); + m_started = false; } + } - private: - DWORD m_flags = 0; - DWORD m_queue = 0; + private: + bool m_started = false; }; -class SyncMFCallback - : public MFCallbackBase -{ - public: - SyncMFCallback() { m_invokeEvent.create(); } - - void Wait(uint32_t timeout = INFINITE) - { - if(!m_invokeEvent.wait(timeout)) - { - THROW_HR(ERROR_TIMEOUT); - } - } - - IMFAsyncResult* GetResult() { return m_result.get(); } +class MFCallbackBase + : public winrt::implements { + public: + MFCallbackBase(DWORD flags = 0, + DWORD queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED) + : m_flags(flags), m_queue(queue) {} + + DWORD GetQueue() const { return m_queue; } + DWORD GetFlags() const { return m_flags; } + + // IMFAsyncCallback methods + IFACEMETHODIMP GetParameters(_Out_ DWORD* flags, _Out_ DWORD* queue) { + *flags = m_flags; + *queue = m_queue; + return S_OK; + } + + private: + DWORD m_flags = 0; + DWORD m_queue = 0; +}; - // IMFAsyncCallback methods +class SyncMFCallback : public MFCallbackBase { + public: + SyncMFCallback() { m_invokeEvent.create(); } - IFACEMETHODIMP Invoke(_In_opt_ IMFAsyncResult* result) noexcept override - try - { - m_result.copy_from(result); - m_invokeEvent.SetEvent(); - return S_OK; + void Wait(uint32_t timeout = INFINITE) { + if (!m_invokeEvent.wait(timeout)) { + THROW_HR(ERROR_TIMEOUT); } - CATCH_RETURN(); + } - private: - wil::unique_event m_invokeEvent; - winrt::com_ptr m_result; -}; + IMFAsyncResult* GetResult() { return m_result.get(); } -class MFWorkItem : public MFCallbackBase -{ -public: - MFWorkItem(std::function callback, DWORD flags = 0, DWORD queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED) : MFCallbackBase(flags, queue) - { - m_callback = callback; - } + // IMFAsyncCallback methods - // IMFAsyncCallback methods + IFACEMETHODIMP Invoke(_In_opt_ IMFAsyncResult* result) noexcept override try { + m_result.copy_from(result); + m_invokeEvent.SetEvent(); + return S_OK; + } + CATCH_RETURN(); - IFACEMETHODIMP Invoke(_In_opt_ IMFAsyncResult* /*result*/) noexcept override - try - { - m_callback(); - return S_OK; - } - CATCH_RETURN(); + private: + wil::unique_event m_invokeEvent; + winrt::com_ptr m_result; +}; -private: - std::function m_callback; +class MFWorkItem : public MFCallbackBase { + public: + MFWorkItem(std::function callback, + DWORD flags = 0, + DWORD queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED) + : MFCallbackBase(flags, queue) { + m_callback = callback; + } + + // IMFAsyncCallback methods + + IFACEMETHODIMP Invoke(_In_opt_ IMFAsyncResult* /*result*/) noexcept override + try { + m_callback(); + return S_OK; + } + CATCH_RETURN(); + + private: + std::function m_callback; }; -inline void MFPutWorkItem(std::function callback) -{ - winrt::com_ptr workItem = winrt::make_self(callback); - THROW_IF_FAILED(MFPutWorkItem2(workItem->GetQueue(), 0, workItem.get(), nullptr)); +inline void MFPutWorkItem(std::function callback) { + winrt::com_ptr workItem = winrt::make_self(callback); + THROW_IF_FAILED( + MFPutWorkItem2(workItem->GetQueue(), 0, workItem.get(), nullptr)); } -// Helper function for ensuring that the provided callback runs synchronously on a MTA thread. -// All MediaFoundation calls should be made on a MTA thread to avoid subtle deadlock bugs due to objects inadvertedly being created in a STA -inline void RunSyncInMTA(std::function callback) -{ - APTTYPE apartmentType = {}; - APTTYPEQUALIFIER qualifier = {}; - - THROW_IF_FAILED(CoGetApartmentType(&apartmentType, &qualifier)); - - if(apartmentType == APTTYPE_MTA) - { - wil::unique_couninitialize_call unique_coinit; - if(qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA) - { - unique_coinit = wil::CoInitializeEx_failfast(COINIT_MULTITHREADED); - } - callback(); - } - else - { - wil::unique_event complete; - complete.create(); - MFPutWorkItem([&](){ - callback(); - complete.SetEvent(); - }); - complete.wait(); +// Helper function for ensuring that the provided callback runs synchronously on +// a MTA thread. All MediaFoundation calls should be made on a MTA thread to +// avoid subtle deadlock bugs due to objects inadvertedly being created in a STA +inline void RunSyncInMTA(std::function callback) { + APTTYPE apartmentType = {}; + APTTYPEQUALIFIER qualifier = {}; + + THROW_IF_FAILED(CoGetApartmentType(&apartmentType, &qualifier)); + + if (apartmentType == APTTYPE_MTA) { + wil::unique_couninitialize_call unique_coinit; + if (qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA) { + unique_coinit = wil::CoInitializeEx_failfast(COINIT_MULTITHREADED); } + callback(); + } else { + wil::unique_event complete; + complete.create(); + MFPutWorkItem([&]() { + callback(); + complete.SetEvent(); + }); + complete.wait(); + } } -} // namespace media +} // namespace media diff --git a/packages/audioplayers_windows/windows/audio_player.cpp b/packages/audioplayers_windows/windows/audio_player.cpp index f2fd798ab..a0a8934d8 100644 --- a/packages/audioplayers_windows/windows/audio_player.cpp +++ b/packages/audioplayers_windows/windows/audio_player.cpp @@ -22,239 +22,243 @@ AudioPlayer::AudioPlayer( : _playerId(playerId), _methodChannel(methodChannel), _eventHandler(eventHandler) { - m_mfPlatform.Startup(); - - // Callbacks invoked by the media engine wrapper - auto onError = std::bind(&AudioPlayer::OnMediaError, this, - std::placeholders::_1, std::placeholders::_2); - auto onBufferingStateChanged = std::bind(&AudioPlayer::OnMediaStateChange, - this, std::placeholders::_1); - auto onPlaybackEndedCB = std::bind(&AudioPlayer::OnPlaybackEnded, this); - auto onTimeUpdateCB = std::bind(&AudioPlayer::OnTimeUpdate, this); - auto onSeekCompletedCB = std::bind(&AudioPlayer::OnSeekCompleted, this); - auto onLoadedCB = std::bind(&AudioPlayer::SendInitialized, this); - - // Create and initialize the MediaEngineWrapper which manages media playback - m_mediaEngineWrapper = winrt::make_self( - onLoadedCB, onError, onBufferingStateChanged, onPlaybackEndedCB, - onTimeUpdateCB, onSeekCompletedCB); - - m_mediaEngineWrapper->Initialize(); + m_mfPlatform.Startup(); + + // Callbacks invoked by the media engine wrapper + auto onError = std::bind(&AudioPlayer::OnMediaError, this, + std::placeholders::_1, std::placeholders::_2); + auto onBufferingStateChanged = + std::bind(&AudioPlayer::OnMediaStateChange, this, std::placeholders::_1); + auto onPlaybackEndedCB = std::bind(&AudioPlayer::OnPlaybackEnded, this); + auto onTimeUpdateCB = std::bind(&AudioPlayer::OnTimeUpdate, this); + auto onSeekCompletedCB = std::bind(&AudioPlayer::OnSeekCompleted, this); + auto onLoadedCB = std::bind(&AudioPlayer::SendInitialized, this); + + // Create and initialize the MediaEngineWrapper which manages media playback + m_mediaEngineWrapper = winrt::make_self( + onLoadedCB, onError, onBufferingStateChanged, onPlaybackEndedCB, + onTimeUpdateCB, onSeekCompletedCB); + + m_mediaEngineWrapper->Initialize(); } // This method should be called asynchronously, to avoid freezing UI void AudioPlayer::SetSourceUrl(std::string url) { - if (_url != url) { - _url = url; - _isInitialized = false; - - try { - // Create a source resolver to create an IMFMediaSource for the content - // URL. This will create an instance of an inbuilt OS media source for - // playback. An application can skip this step and instantiate a custom - // IMFMediaSource implementation instead. - winrt::com_ptr sourceResolver; - THROW_IF_FAILED(MFCreateSourceResolver(sourceResolver.put())); - constexpr uint32_t sourceResolutionFlags = - MF_RESOLUTION_MEDIASOURCE | - MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE | - MF_RESOLUTION_READ; - MF_OBJECT_TYPE objectType = {}; - - winrt::com_ptr mediaSource; - THROW_IF_FAILED(sourceResolver->CreateObjectFromURL( - winrt::to_hstring(url).c_str(), sourceResolutionFlags, nullptr, - &objectType, reinterpret_cast(mediaSource.put_void()))); - - m_mediaEngineWrapper->SetMediaSource(mediaSource.get()); - } catch (...) { - // Forward errors to event stream, as this is called asynchronously - this->OnError("WindowsAudioError", "Error setting url to '" + url + "'.", nullptr); - } - } else { - OnPrepared(true); + if (_url != url) { + _url = url; + _isInitialized = false; + + try { + // Create a source resolver to create an IMFMediaSource for the content + // URL. This will create an instance of an inbuilt OS media source for + // playback. An application can skip this step and instantiate a custom + // IMFMediaSource implementation instead. + winrt::com_ptr sourceResolver; + THROW_IF_FAILED(MFCreateSourceResolver(sourceResolver.put())); + constexpr uint32_t sourceResolutionFlags = + MF_RESOLUTION_MEDIASOURCE | + MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE | + MF_RESOLUTION_READ; + MF_OBJECT_TYPE objectType = {}; + + winrt::com_ptr mediaSource; + THROW_IF_FAILED(sourceResolver->CreateObjectFromURL( + winrt::to_hstring(url).c_str(), sourceResolutionFlags, nullptr, + &objectType, reinterpret_cast(mediaSource.put_void()))); + + m_mediaEngineWrapper->SetMediaSource(mediaSource.get()); + } catch (...) { + // Forward errors to event stream, as this is called asynchronously + this->OnError("WindowsAudioError", "Error setting url to '" + url + "'.", + nullptr); } + } else { + OnPrepared(true); + } } AudioPlayer::~AudioPlayer() {} void AudioPlayer::OnMediaError(MF_MEDIA_ENGINE_ERR error, HRESULT hr) { - LOG_HR_MSG(hr, "MediaEngine error (%d)", error); - // TODO(Gustl22): adapt log message to dart error event, check stacktrace. - if (this->_eventHandler) { - _com_error err(hr); - - std::wstring wstr(err.ErrorMessage()); - - int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &wstr[0], - (int)wstr.size(), NULL, 0, NULL, NULL); - std::string ret = std::string(size, 0); - WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &wstr[0], - (int)wstr.size(), &ret[0], size, NULL, NULL); - - std::string message = "MediaEngine error"; - this->OnError(std::to_string(error), message, - flutter::EncodableValue(ret)); - } + LOG_HR_MSG(hr, "MediaEngine error (%d)", error); + // TODO(Gustl22): adapt log message to dart error event, check stacktrace. + if (this->_eventHandler) { + _com_error err(hr); + + std::wstring wstr(err.ErrorMessage()); + + int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &wstr[0], + (int)wstr.size(), NULL, 0, NULL, NULL); + std::string ret = std::string(size, 0); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &wstr[0], + (int)wstr.size(), &ret[0], size, NULL, NULL); + + std::string message = "MediaEngine error"; + this->OnError(std::to_string(error), message, flutter::EncodableValue(ret)); + } } -void AudioPlayer::OnError(const std::string& code, const std::string& message, +void AudioPlayer::OnError(const std::string& code, + const std::string& message, const flutter::EncodableValue& details) { - if (this->_eventHandler) { - this->_eventHandler->Error(code, message, details); - } + if (this->_eventHandler) { + this->_eventHandler->Error(code, message, details); + } } void AudioPlayer::OnMediaStateChange( media::MediaEngineWrapper::BufferingState bufferingState) { - if (bufferingState != - media::MediaEngineWrapper::BufferingState::HAVE_NOTHING) { - // TODO(Gustl22): add buffering state - } + if (bufferingState != + media::MediaEngineWrapper::BufferingState::HAVE_NOTHING) { + // TODO(Gustl22): add buffering state + } } void AudioPlayer::OnPrepared(bool isPrepared) { - if (this->_eventHandler) { - this->_eventHandler->Success( - std::make_unique(flutter::EncodableMap( - {{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onPrepared")}, - {flutter::EncodableValue("value"), - flutter::EncodableValue(isPrepared)}}))); - } + if (this->_eventHandler) { + this->_eventHandler->Success(std::make_unique( + flutter::EncodableMap({{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onPrepared")}, + {flutter::EncodableValue("value"), + flutter::EncodableValue(isPrepared)}}))); + } } void AudioPlayer::OnPlaybackEnded() { - SeekTo(0); - if (GetLooping()) { - Play(); - } - if (this->_eventHandler) { - this->_eventHandler->Success( - std::make_unique(flutter::EncodableMap( - {{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onComplete")}, - {flutter::EncodableValue("value"), - flutter::EncodableValue(true)}}))); - } + SeekTo(0); + if (GetLooping()) { + Play(); + } + if (this->_eventHandler) { + this->_eventHandler->Success(std::make_unique( + flutter::EncodableMap({{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onComplete")}, + {flutter::EncodableValue("value"), + flutter::EncodableValue(true)}}))); + } } void AudioPlayer::OnTimeUpdate() { - if (this->_eventHandler) { - auto position = m_mediaEngineWrapper->GetMediaTime(); - this->_eventHandler->Success( - std::make_unique(flutter::EncodableMap( - {{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onCurrentPosition")}, - {flutter::EncodableValue("value"), - isnan(position) ? flutter::EncodableValue(std::monostate{}) - : flutter::EncodableValue( - ConvertSecondsToMs(position))}}))); - } + if (this->_eventHandler) { + auto position = m_mediaEngineWrapper->GetMediaTime(); + this->_eventHandler->Success( + std::make_unique(flutter::EncodableMap( + {{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onCurrentPosition")}, + {flutter::EncodableValue("value"), + isnan(position) + ? flutter::EncodableValue(std::monostate{}) + : flutter::EncodableValue(ConvertSecondsToMs(position))}}))); + } } void AudioPlayer::OnDurationUpdate() { - auto duration = m_mediaEngineWrapper->GetDuration(); - if (this->_eventHandler) { - this->_eventHandler->Success( - std::make_unique(flutter::EncodableMap( - {{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onDuration")}, - {flutter::EncodableValue("value"), - isnan(duration) ? flutter::EncodableValue(std::monostate{}) - : flutter::EncodableValue( - ConvertSecondsToMs(duration))}}))); - } + auto duration = m_mediaEngineWrapper->GetDuration(); + if (this->_eventHandler) { + this->_eventHandler->Success( + std::make_unique(flutter::EncodableMap( + {{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onDuration")}, + {flutter::EncodableValue("value"), + isnan(duration) + ? flutter::EncodableValue(std::monostate{}) + : flutter::EncodableValue(ConvertSecondsToMs(duration))}}))); + } } void AudioPlayer::OnSeekCompleted() { - if (this->_eventHandler) { - this->_eventHandler->Success( - std::make_unique(flutter::EncodableMap( - {{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onSeekComplete")}, - {flutter::EncodableValue("value"), - flutter::EncodableValue(true)}}))); - } + if (this->_eventHandler) { + this->_eventHandler->Success( + std::make_unique(flutter::EncodableMap( + {{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onSeekComplete")}, + {flutter::EncodableValue("value"), + flutter::EncodableValue(true)}}))); + } } void AudioPlayer::OnLog(const std::string& message) { - this->_eventHandler->Success(std::make_unique( - flutter::EncodableMap({{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onLog")}, - {flutter::EncodableValue("value"), - flutter::EncodableValue(message)}}))); + this->_eventHandler->Success(std::make_unique( + flutter::EncodableMap({{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onLog")}, + {flutter::EncodableValue("value"), + flutter::EncodableValue(message)}}))); } void AudioPlayer::SendInitialized() { - if (!this->_isInitialized) { - this->_isInitialized = true; - OnPrepared(true); - OnDurationUpdate(); - OnTimeUpdate(); - } + if (!this->_isInitialized) { + this->_isInitialized = true; + OnPrepared(true); + OnDurationUpdate(); + OnTimeUpdate(); + } } void AudioPlayer::ReleaseMediaSource() { - if (_isInitialized) { - m_mediaEngineWrapper->Pause(); - } - m_mediaEngineWrapper->ReleaseMediaSource(); - _isInitialized = false; + if (_isInitialized) { + m_mediaEngineWrapper->Pause(); + } + m_mediaEngineWrapper->ReleaseMediaSource(); + _isInitialized = false; } void AudioPlayer::Dispose() { - ReleaseMediaSource(); - m_mediaEngineWrapper->Shutdown(); - _methodChannel = nullptr; - _eventHandler = nullptr; + ReleaseMediaSource(); + m_mediaEngineWrapper->Shutdown(); + _methodChannel = nullptr; + _eventHandler = nullptr; } void AudioPlayer::SetLooping(bool isLooping) { - m_mediaEngineWrapper->SetLooping(isLooping); + m_mediaEngineWrapper->SetLooping(isLooping); } -bool AudioPlayer::GetLooping() { return m_mediaEngineWrapper->GetLooping(); } +bool AudioPlayer::GetLooping() { + return m_mediaEngineWrapper->GetLooping(); +} void AudioPlayer::SetVolume(double volume) { - if (volume > 1) { - volume = 1; - } else if (volume < 0) { - volume = 0; - } - m_mediaEngineWrapper->SetVolume((float)volume); + if (volume > 1) { + volume = 1; + } else if (volume < 0) { + volume = 0; + } + m_mediaEngineWrapper->SetVolume((float)volume); } void AudioPlayer::SetPlaybackSpeed(double playbackSpeed) { - m_mediaEngineWrapper->SetPlaybackRate(playbackSpeed); + m_mediaEngineWrapper->SetPlaybackRate(playbackSpeed); } void AudioPlayer::SetBalance(double balance) { - m_mediaEngineWrapper->SetBalance(balance); + m_mediaEngineWrapper->SetBalance(balance); } void AudioPlayer::Play() { - m_mediaEngineWrapper->StartPlayingFrom( - m_mediaEngineWrapper->GetMediaTime()); - OnDurationUpdate(); + m_mediaEngineWrapper->StartPlayingFrom(m_mediaEngineWrapper->GetMediaTime()); + OnDurationUpdate(); } -void AudioPlayer::Pause() { m_mediaEngineWrapper->Pause(); } +void AudioPlayer::Pause() { + m_mediaEngineWrapper->Pause(); +} void AudioPlayer::Resume() { - m_mediaEngineWrapper->Resume(); - OnDurationUpdate(); + m_mediaEngineWrapper->Resume(); + OnDurationUpdate(); } double AudioPlayer::GetPosition() { - if(!_isInitialized) { - return std::numeric_limits::quiet_NaN(); - } - return m_mediaEngineWrapper->GetMediaTime(); + if (!_isInitialized) { + return std::numeric_limits::quiet_NaN(); + } + return m_mediaEngineWrapper->GetMediaTime(); } double AudioPlayer::GetDuration() { - return m_mediaEngineWrapper->GetDuration(); + return m_mediaEngineWrapper->GetDuration(); } -void AudioPlayer::SeekTo(double seek) { m_mediaEngineWrapper->SeekTo(seek); } +void AudioPlayer::SeekTo(double seek) { + m_mediaEngineWrapper->SeekTo(seek); +} diff --git a/packages/audioplayers_windows/windows/audio_player.h b/packages/audioplayers_windows/windows/audio_player.h index 27575353c..f9f7137e3 100644 --- a/packages/audioplayers_windows/windows/audio_player.h +++ b/packages/audioplayers_windows/windows/audio_player.h @@ -46,74 +46,75 @@ using namespace winrt; class AudioPlayer { - public: - AudioPlayer(std::string playerId, - flutter::MethodChannel* methodChannel, - EventStreamHandler<>* eventHandler); + public: + AudioPlayer(std::string playerId, + flutter::MethodChannel* methodChannel, + EventStreamHandler<>* eventHandler); - void Dispose(); + void Dispose(); - void ReleaseMediaSource(); + void ReleaseMediaSource(); - void SetLooping(bool isLooping); + void SetLooping(bool isLooping); - void SetVolume(double volume); + void SetVolume(double volume); - void SetPlaybackSpeed(double playbackSpeed); + void SetPlaybackSpeed(double playbackSpeed); - void SetBalance(double balance); + void SetBalance(double balance); - void Play(); + void Play(); - void Pause(); + void Pause(); - void Resume(); + void Resume(); - bool GetLooping(); + bool GetLooping(); - double GetPosition(); + double GetPosition(); - double GetDuration(); + double GetDuration(); - void SeekTo(double seek); + void SeekTo(double seek); - void SetSourceUrl(std::string url); + void SetSourceUrl(std::string url); - void OnLog(const std::string& message); + void OnLog(const std::string& message); - void OnError(const std::string& code, const std::string& message, - const flutter::EncodableValue& details); + void OnError(const std::string& code, + const std::string& message, + const flutter::EncodableValue& details); - virtual ~AudioPlayer(); + virtual ~AudioPlayer(); - private: - // Media members - media::MFPlatformRef m_mfPlatform; - winrt::com_ptr m_mediaEngineWrapper; + private: + // Media members + media::MFPlatformRef m_mfPlatform; + winrt::com_ptr m_mediaEngineWrapper; - bool _isInitialized = false; - std::string _url{}; + bool _isInitialized = false; + std::string _url{}; - void SendInitialized(); + void SendInitialized(); - void OnMediaError(MF_MEDIA_ENGINE_ERR error, HRESULT hr); + void OnMediaError(MF_MEDIA_ENGINE_ERR error, HRESULT hr); - void OnMediaStateChange( - media::MediaEngineWrapper::BufferingState bufferingState); + void OnMediaStateChange( + media::MediaEngineWrapper::BufferingState bufferingState); - void OnPlaybackEnded(); + void OnPlaybackEnded(); - void OnDurationUpdate(); + void OnDurationUpdate(); - void OnTimeUpdate(); + void OnTimeUpdate(); - void OnSeekCompleted(); + void OnSeekCompleted(); - void OnPrepared(bool isPrepared); + void OnPrepared(bool isPrepared); - std::string _playerId; + std::string _playerId; - flutter::MethodChannel* _methodChannel; + flutter::MethodChannel* _methodChannel; - EventStreamHandler<>* _eventHandler; + EventStreamHandler<>* _eventHandler; }; diff --git a/packages/audioplayers_windows/windows/audioplayers_helpers.h b/packages/audioplayers_windows/windows/audioplayers_helpers.h index c3dccedc8..b35dc68fc 100644 --- a/packages/audioplayers_windows/windows/audioplayers_helpers.h +++ b/packages/audioplayers_windows/windows/audioplayers_helpers.h @@ -1,15 +1,13 @@ constexpr int64_t c_msPerSecond = 1000; -template -inline int64_t ConvertSecondsToMs(SecondsT seconds) -{ - if (isinf(seconds)) - return 0; - return static_cast(seconds * c_msPerSecond); +template +inline int64_t ConvertSecondsToMs(SecondsT seconds) { + if (isinf(seconds)) + return 0; + return static_cast(seconds * c_msPerSecond); } -template -inline double ConvertMsToSeconds(MsT ms) -{ - return static_cast(ms) / c_msPerSecond; +template +inline double ConvertMsToSeconds(MsT ms) { + return static_cast(ms) / c_msPerSecond; } diff --git a/packages/audioplayers_windows/windows/audioplayers_windows_plugin.cpp b/packages/audioplayers_windows/windows/audioplayers_windows_plugin.cpp index ebf81c443..8cbd80894 100644 --- a/packages/audioplayers_windows/windows/audioplayers_windows_plugin.cpp +++ b/packages/audioplayers_windows/windows/audioplayers_windows_plugin.cpp @@ -23,83 +23,82 @@ namespace { using namespace flutter; template -T GetArgument(const std::string arg, const EncodableValue *args, T fallback) { - T result{fallback}; - const auto *arguments = std::get_if(args); - if (arguments) { - auto result_it = arguments->find(EncodableValue(arg)); - if (result_it != arguments->end()) { - if (!result_it->second.IsNull()) - result = std::get(result_it->second); - } +T GetArgument(const std::string arg, const EncodableValue* args, T fallback) { + T result{fallback}; + const auto* arguments = std::get_if(args); + if (arguments) { + auto result_it = arguments->find(EncodableValue(arg)); + if (result_it != arguments->end()) { + if (!result_it->second.IsNull()) + result = std::get(result_it->second); } - return result; + } + return result; } class AudioplayersWindowsPlugin : public Plugin { - public: - static void RegisterWithRegistrar(PluginRegistrarWindows *registrar); + public: + static void RegisterWithRegistrar(PluginRegistrarWindows* registrar); - AudioplayersWindowsPlugin(); + AudioplayersWindowsPlugin(); - virtual ~AudioplayersWindowsPlugin(); + virtual ~AudioplayersWindowsPlugin(); - private: - std::map> audioPlayers; + private: + std::map> audioPlayers; - static inline BinaryMessenger *binaryMessenger; - static inline std::unique_ptr> methods{}; - static inline std::unique_ptr> - globalMethods{}; - static inline std::unique_ptr> globalEvents{}; + static inline BinaryMessenger* binaryMessenger; + static inline std::unique_ptr> methods{}; + static inline std::unique_ptr> globalMethods{}; + static inline std::unique_ptr> globalEvents{}; - // Called when a method is called on this plugin's channel from Dart. - void HandleMethodCall(const MethodCall &method_call, - std::unique_ptr> result); + // Called when a method is called on this plugin's channel from Dart. + void HandleMethodCall(const MethodCall& method_call, + std::unique_ptr> result); - void HandleGlobalMethodCall( - const MethodCall &method_call, - std::unique_ptr> result); + void HandleGlobalMethodCall( + const MethodCall& method_call, + std::unique_ptr> result); - void CreatePlayer(std::string playerId); + void CreatePlayer(std::string playerId); - AudioPlayer *GetPlayer(std::string playerId); + AudioPlayer* GetPlayer(std::string playerId); - void OnGlobalLog(const std::string& message); + void OnGlobalLog(const std::string& message); }; // static void AudioplayersWindowsPlugin::RegisterWithRegistrar( - PluginRegistrarWindows *registrar) { - binaryMessenger = registrar->messenger(); - methods = std::make_unique>( - binaryMessenger, "xyz.luan/audioplayers", - &StandardMethodCodec::GetInstance()); - globalMethods = std::make_unique>( - binaryMessenger, "xyz.luan/audioplayers.global", - &StandardMethodCodec::GetInstance()); - auto _globalEventChannel = std::make_unique>( - binaryMessenger, "xyz.luan/audioplayers.global/events", - &StandardMethodCodec::GetInstance()); - - auto plugin = std::make_unique(); - - methods->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto &call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - - globalMethods->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto &call, auto result) { - plugin_pointer->HandleGlobalMethodCall(call, std::move(result)); - }); - globalEvents = std::make_unique>(); - auto _obj_stm_handle = - static_cast *>(globalEvents.get()); - std::unique_ptr> _ptr{_obj_stm_handle}; - _globalEventChannel->SetStreamHandler(std::move(_ptr)); - - registrar->AddPlugin(std::move(plugin)); + PluginRegistrarWindows* registrar) { + binaryMessenger = registrar->messenger(); + methods = std::make_unique>( + binaryMessenger, "xyz.luan/audioplayers", + &StandardMethodCodec::GetInstance()); + globalMethods = std::make_unique>( + binaryMessenger, "xyz.luan/audioplayers.global", + &StandardMethodCodec::GetInstance()); + auto _globalEventChannel = std::make_unique>( + binaryMessenger, "xyz.luan/audioplayers.global/events", + &StandardMethodCodec::GetInstance()); + + auto plugin = std::make_unique(); + + methods->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto& call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + globalMethods->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto& call, auto result) { + plugin_pointer->HandleGlobalMethodCall(call, std::move(result)); + }); + globalEvents = std::make_unique>(); + auto _obj_stm_handle = + static_cast*>(globalEvents.get()); + std::unique_ptr> _ptr{_obj_stm_handle}; + _globalEventChannel->SetStreamHandler(std::move(_ptr)); + + registrar->AddPlugin(std::move(plugin)); } AudioplayersWindowsPlugin::AudioplayersWindowsPlugin() {} @@ -107,165 +106,166 @@ AudioplayersWindowsPlugin::AudioplayersWindowsPlugin() {} AudioplayersWindowsPlugin::~AudioplayersWindowsPlugin() {} void AudioplayersWindowsPlugin::HandleGlobalMethodCall( - const MethodCall &method_call, + const MethodCall& method_call, std::unique_ptr> result) { - auto args = method_call.arguments(); - - if (method_call.method_name().compare("setAudioContext") == 0) { - this->OnGlobalLog("Setting AudioContext is not supported on Windows"); - } else if (method_call.method_name().compare("emitLog") == 0) { - auto message = GetArgument("message", args, std::string()); - this->OnGlobalLog(message); - } else if (method_call.method_name().compare("emitError") == 0) { - auto code = GetArgument("code", args, std::string()); - auto message = GetArgument("message", args, std::string()); - globalEvents->Error(code, message, nullptr); - result->Success(); - } else { - result->NotImplemented(); - return; - } - + auto args = method_call.arguments(); + + if (method_call.method_name().compare("setAudioContext") == 0) { + this->OnGlobalLog("Setting AudioContext is not supported on Windows"); + } else if (method_call.method_name().compare("emitLog") == 0) { + auto message = GetArgument("message", args, std::string()); + this->OnGlobalLog(message); + } else if (method_call.method_name().compare("emitError") == 0) { + auto code = GetArgument("code", args, std::string()); + auto message = GetArgument("message", args, std::string()); + globalEvents->Error(code, message, nullptr); result->Success(); + } else { + result->NotImplemented(); + return; + } + + result->Success(); } void AudioplayersWindowsPlugin::HandleMethodCall( - const MethodCall &method_call, + const MethodCall& method_call, std::unique_ptr> result) { - auto args = method_call.arguments(); + auto args = method_call.arguments(); - auto playerId = GetArgument("playerId", args, std::string()); - if (playerId.empty()) { - result->Error("WindowsAudioError", "Call missing mandatory parameter playerId.", - nullptr); - return; - } + auto playerId = GetArgument("playerId", args, std::string()); + if (playerId.empty()) { + result->Error("WindowsAudioError", + "Call missing mandatory parameter playerId.", nullptr); + return; + } - if (method_call.method_name().compare("create") == 0) { - CreatePlayer(playerId); - result->Success(); - return; - } - - auto player = GetPlayer(playerId); - if(!player) { - result->Error( - "WindowsAudioError", - "Player has not yet been created or has already been disposed.", - nullptr); - return; + if (method_call.method_name().compare("create") == 0) { + CreatePlayer(playerId); + result->Success(); + return; + } + + auto player = GetPlayer(playerId); + if (!player) { + result->Error( + "WindowsAudioError", + "Player has not yet been created or has already been disposed.", + nullptr); + return; + } + + if (method_call.method_name().compare("pause") == 0) { + player->Pause(); + } else if (method_call.method_name().compare("resume") == 0) { + player->Resume(); + } else if (method_call.method_name().compare("stop") == 0) { + player->Pause(); + player->SeekTo(0); + } else if (method_call.method_name().compare("release") == 0) { + player->ReleaseMediaSource(); + } else if (method_call.method_name().compare("seek") == 0) { + auto positionInMs = GetArgument( + "position", args, (int)ConvertSecondsToMs(player->GetPosition())); + player->SeekTo(ConvertMsToSeconds(positionInMs)); + } else if (method_call.method_name().compare("setSourceUrl") == 0) { + auto url = GetArgument("url", args, std::string()); + + if (url.empty()) { + result->Error("WindowsAudioError", "Null URL received on setSourceUrl", + nullptr); + return; } - if (method_call.method_name().compare("pause") == 0) { - player->Pause(); - } else if (method_call.method_name().compare("resume") == 0) { - player->Resume(); - } else if (method_call.method_name().compare("stop") == 0) { - player->Pause(); - player->SeekTo(0); - } else if (method_call.method_name().compare("release") == 0) { - player->ReleaseMediaSource(); - } else if (method_call.method_name().compare("seek") == 0) { - auto positionInMs = GetArgument("position", args, - (int)ConvertSecondsToMs(player->GetPosition())); - player->SeekTo(ConvertMsToSeconds(positionInMs)); - } else if (method_call.method_name().compare("setSourceUrl") == 0) { - auto url = GetArgument("url", args, std::string()); - - if (url.empty()) { - result->Error("WindowsAudioError", "Null URL received on setSourceUrl", nullptr); - return; - } - - std::thread(&AudioPlayer::SetSourceUrl, player, url).detach(); - } else if (method_call.method_name().compare("getDuration") == 0) { - auto duration = player->GetDuration(); - result->Success(isnan(duration) - ? EncodableValue(std::monostate{}) - : EncodableValue(ConvertSecondsToMs(duration))); - return; - } else if (method_call.method_name().compare("setVolume") == 0) { - auto volume = GetArgument("volume", args, 1.0); - player->SetVolume(volume); - } else if (method_call.method_name().compare("getCurrentPosition") == 0) { - auto position = player->GetPosition(); - result->Success(isnan(position) - ? EncodableValue(std::monostate{}) - : EncodableValue(ConvertSecondsToMs(position))); - return; - } else if (method_call.method_name().compare("setPlaybackRate") == 0) { - auto playbackRate = GetArgument("playbackRate", args, 1.0); - player->SetPlaybackSpeed(playbackRate); - } else if (method_call.method_name().compare("setReleaseMode") == 0) { - auto releaseMode = - GetArgument("releaseMode", args, std::string()); - if (releaseMode.empty()) { - result->Error( - "WindowsAudioError", "Error calling setReleaseMode, releaseMode cannot be null", - nullptr); - return; - } - auto looping = releaseMode.find("loop") != std::string::npos; - player->SetLooping(looping); - } else if (method_call.method_name().compare("setPlayerMode") == 0) { - // windows doesn't have multiple player modes, so this should no-op - } else if (method_call.method_name().compare("setBalance") == 0) { - auto balance = GetArgument("balance", args, 0.0); - player->SetBalance(balance); - } else if (method_call.method_name().compare("emitLog") == 0) { - auto message = GetArgument("message", args, std::string()); - player->OnLog(message); - } else if (method_call.method_name().compare("emitError") == 0) { - auto code = GetArgument("code", args, std::string()); - auto message = GetArgument("message", args, std::string()); - player->OnError(code, message, nullptr); - } else if (method_call.method_name().compare("dispose") == 0) { - player->Dispose(); - audioPlayers.erase(playerId); - } else { - result->NotImplemented(); - return; + std::thread(&AudioPlayer::SetSourceUrl, player, url).detach(); + } else if (method_call.method_name().compare("getDuration") == 0) { + auto duration = player->GetDuration(); + result->Success(isnan(duration) + ? EncodableValue(std::monostate{}) + : EncodableValue(ConvertSecondsToMs(duration))); + return; + } else if (method_call.method_name().compare("setVolume") == 0) { + auto volume = GetArgument("volume", args, 1.0); + player->SetVolume(volume); + } else if (method_call.method_name().compare("getCurrentPosition") == 0) { + auto position = player->GetPosition(); + result->Success(isnan(position) + ? EncodableValue(std::monostate{}) + : EncodableValue(ConvertSecondsToMs(position))); + return; + } else if (method_call.method_name().compare("setPlaybackRate") == 0) { + auto playbackRate = GetArgument("playbackRate", args, 1.0); + player->SetPlaybackSpeed(playbackRate); + } else if (method_call.method_name().compare("setReleaseMode") == 0) { + auto releaseMode = + GetArgument("releaseMode", args, std::string()); + if (releaseMode.empty()) { + result->Error("WindowsAudioError", + "Error calling setReleaseMode, releaseMode cannot be null", + nullptr); + return; } - result->Success(); + auto looping = releaseMode.find("loop") != std::string::npos; + player->SetLooping(looping); + } else if (method_call.method_name().compare("setPlayerMode") == 0) { + // windows doesn't have multiple player modes, so this should no-op + } else if (method_call.method_name().compare("setBalance") == 0) { + auto balance = GetArgument("balance", args, 0.0); + player->SetBalance(balance); + } else if (method_call.method_name().compare("emitLog") == 0) { + auto message = GetArgument("message", args, std::string()); + player->OnLog(message); + } else if (method_call.method_name().compare("emitError") == 0) { + auto code = GetArgument("code", args, std::string()); + auto message = GetArgument("message", args, std::string()); + player->OnError(code, message, nullptr); + } else if (method_call.method_name().compare("dispose") == 0) { + player->Dispose(); + audioPlayers.erase(playerId); + } else { + result->NotImplemented(); + return; + } + result->Success(); } void AudioplayersWindowsPlugin::CreatePlayer(std::string playerId) { - auto eventChannel = std::make_unique>( - binaryMessenger, "xyz.luan/audioplayers/events/" + playerId, - &StandardMethodCodec::GetInstance()); - - auto eventHandler = new EventStreamHandler<>(); - auto _obj_stm_handle = - static_cast *>(eventHandler); - std::unique_ptr> _ptr{_obj_stm_handle}; - eventChannel->SetStreamHandler(std::move(_ptr)); - - auto player = - std::make_unique(playerId, methods.get(), eventHandler); - audioPlayers.insert(std::make_pair(playerId, std::move(player))); + auto eventChannel = std::make_unique>( + binaryMessenger, "xyz.luan/audioplayers/events/" + playerId, + &StandardMethodCodec::GetInstance()); + + auto eventHandler = new EventStreamHandler<>(); + auto _obj_stm_handle = + static_cast*>(eventHandler); + std::unique_ptr> _ptr{_obj_stm_handle}; + eventChannel->SetStreamHandler(std::move(_ptr)); + + auto player = + std::make_unique(playerId, methods.get(), eventHandler); + audioPlayers.insert(std::make_pair(playerId, std::move(player))); } -AudioPlayer *AudioplayersWindowsPlugin::GetPlayer(std::string playerId) { - auto searchPlayer = audioPlayers.find(playerId); - if(searchPlayer == audioPlayers.end()) { - return nullptr; - } - return searchPlayer->second.get(); +AudioPlayer* AudioplayersWindowsPlugin::GetPlayer(std::string playerId) { + auto searchPlayer = audioPlayers.find(playerId); + if (searchPlayer == audioPlayers.end()) { + return nullptr; + } + return searchPlayer->second.get(); } void AudioplayersWindowsPlugin::OnGlobalLog(const std::string& message) { - globalEvents->Success(std::make_unique( - flutter::EncodableMap({{flutter::EncodableValue("event"), - flutter::EncodableValue("audio.onLog")}, - {flutter::EncodableValue("value"), - flutter::EncodableValue(message)}}))); + globalEvents->Success(std::make_unique( + flutter::EncodableMap({{flutter::EncodableValue("event"), + flutter::EncodableValue("audio.onLog")}, + {flutter::EncodableValue("value"), + flutter::EncodableValue(message)}}))); } } // namespace void AudioplayersWindowsPluginRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { - AudioplayersWindowsPlugin::RegisterWithRegistrar( - PluginRegistrarManager::GetInstance() - ->GetRegistrar(registrar)); + AudioplayersWindowsPlugin::RegisterWithRegistrar( + PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); } diff --git a/packages/audioplayers_windows/windows/event_stream_handler.h b/packages/audioplayers_windows/windows/event_stream_handler.h index 3e24cba18..577f4c0ef 100644 --- a/packages/audioplayers_windows/windows/event_stream_handler.h +++ b/packages/audioplayers_windows/windows/event_stream_handler.h @@ -1,5 +1,5 @@ -#include #include +#include #include @@ -7,39 +7,42 @@ using namespace flutter; template class EventStreamHandler : public StreamHandler { - public: - EventStreamHandler() = default; - - virtual ~EventStreamHandler() = default; - - void Success(std::unique_ptr _data) { - std::unique_lock _ul(m_mtx); - if (m_sink.get()) m_sink.get()->Success(*_data.get()); - } - - void Error(const std::string& error_code, const std::string& error_message, - const T& error_details) { - std::unique_lock _ul(m_mtx); - if (m_sink.get()) - m_sink.get()->Error(error_code, error_message, error_details); - } - - protected: - std::unique_ptr> OnListenInternal( - const T* arguments, std::unique_ptr>&& events) override { - std::unique_lock _ul(m_mtx); - m_sink = std::move(events); - return nullptr; - } - - std::unique_ptr> OnCancelInternal( - const T* arguments) override { - std::unique_lock _ul(m_mtx); - m_sink.release(); - return nullptr; - } - - private: - std::mutex m_mtx; - std::unique_ptr> m_sink; + public: + EventStreamHandler() = default; + + virtual ~EventStreamHandler() = default; + + void Success(std::unique_ptr _data) { + std::unique_lock _ul(m_mtx); + if (m_sink.get()) + m_sink.get()->Success(*_data.get()); + } + + void Error(const std::string& error_code, + const std::string& error_message, + const T& error_details) { + std::unique_lock _ul(m_mtx); + if (m_sink.get()) + m_sink.get()->Error(error_code, error_message, error_details); + } + + protected: + std::unique_ptr> OnListenInternal( + const T* arguments, + std::unique_ptr>&& events) override { + std::unique_lock _ul(m_mtx); + m_sink = std::move(events); + return nullptr; + } + + std::unique_ptr> OnCancelInternal( + const T* arguments) override { + std::unique_lock _ul(m_mtx); + m_sink.release(); + return nullptr; + } + + private: + std::mutex m_mtx; + std::unique_ptr> m_sink; }; \ No newline at end of file