diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000000000000000000000000000000000000..12c0fb0c17fbf16a8caa9bab1c65207091cb2b5b --- /dev/null +++ b/OAT.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 770d1939fa74897469d10c04b503371986b7acca..c98e18ee4c39fafbe60ef1f9e48805468802b1f1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,24 @@ +// +// Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + import 'dart:math'; import 'package:audio_session/audio_session.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:just_audio/just_audio.dart' as ja; +import 'package:audioplayers/audioplayers.dart'; void main() { runApp(MyApp()); @@ -14,12 +30,10 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - final _player = ja.AudioPlayer( - // Handle audio_session events ourselves for the purpose of this demo. - handleInterruptions: false, - androidApplyAudioAttributes: false, - handleAudioSessionActivation: false, - ); + final player = AudioPlayer(); + final String url = + "https://luan.xyz/files/audio/ambient_c_motion.mp3"; + PlayerState playerState = PlayerState.stopped; @override void initState() { @@ -32,41 +46,41 @@ class _MyAppState extends State { // Listen to audio interruptions and pause or duck as appropriate. _handleInterruptions(audioSession); // Use another plugin to load audio to play. - await _player.setUrl( - "https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3"); }); + player.play(UrlSource(url)); } void _handleInterruptions(AudioSession audioSession) { - // just_audio can handle interruptions for us, but we have disabled that in - // order to demonstrate manual configuration. bool playInterrupted = false; audioSession.becomingNoisyEventStream.listen((_) { - print('PAUSE'); - _player.pause(); + print('AudioSession PAUSE'); + player.pause(); }); - _player.playingStream.listen((playing) { + player.onPlayerStateChanged.listen((event) { + print('AudioSession state changed: $event'); playInterrupted = false; - if (playing) { + if (event == PlayerState.playing) { audioSession.setActive(true); } + setState(() { + this.playerState = event; + }); }); + audioSession.interruptionEventStream.listen((event) { - print('interruption begin: ${event.begin}'); - print('interruption type: ${event.type}'); + print('AudioSession interruption begin: ${event.begin}'); + print('AudioSession interruption type: ${event.type}'); if (event.begin) { switch (event.type) { case AudioInterruptionType.duck: if (audioSession.androidAudioAttributes!.usage == - AndroidAudioUsage.game) { - _player.setVolume(_player.volume / 2); - } + AndroidAudioUsage.game) {} playInterrupted = false; break; case AudioInterruptionType.pause: case AudioInterruptionType.unknown: - if (_player.playing) { - _player.pause(); + if (this.playerState == PlayerState.playing) { + player.pause(); playInterrupted = true; } break; @@ -74,11 +88,10 @@ class _MyAppState extends State { } else { switch (event.type) { case AudioInterruptionType.duck: - _player.setVolume(min(1.0, _player.volume * 2)); playInterrupted = false; break; case AudioInterruptionType.pause: - if (playInterrupted) _player.play(); + if (playInterrupted) player.play(UrlSource(url)); playInterrupted = false; break; case AudioInterruptionType.unknown: @@ -88,11 +101,16 @@ class _MyAppState extends State { } }); audioSession.devicesChangedEventStream.listen((event) { - print('Devices added: ${event.devicesAdded}'); - print('Devices removed: ${event.devicesRemoved}'); + print('AudioSession Devices added: ${event.devicesAdded}'); + print('AudioSession Devices removed: ${event.devicesRemoved}'); }); } + Future play() async { + await player.stop(); + await player.play(UrlSource(url)); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -106,29 +124,21 @@ class _MyAppState extends State { children: [ Expanded( child: Center( - child: StreamBuilder( - stream: _player.playerStateStream, - builder: (context, snapshot) { - final playerState = snapshot.data; - if (playerState?.processingState != - ja.ProcessingState.ready) { - return Container( - margin: EdgeInsets.all(8.0), - width: 64.0, - height: 64.0, - child: CircularProgressIndicator(), - ); - } else if (playerState?.playing == true) { + child: Builder( + builder: (context) { + if (this.playerState == PlayerState.playing) { + print("AudioSession playing"); return IconButton( icon: Icon(Icons.pause), iconSize: 64.0, - onPressed: _player.pause, + onPressed: player.pause, ); } else { + print("AudioSession stopped"); return IconButton( icon: Icon(Icons.play_arrow), iconSize: 64.0, - onPressed: _player.play, + onPressed: play, ); } }, @@ -151,14 +161,16 @@ class _MyAppState extends State { Text("Input devices", style: Theme.of(context).textTheme.titleLarge), for (var device - in devices.where((device) => device.isInput)) - Text('${device.name} (${device.type.name})'), + in devices.where((device) => device.isInput)) + Text( + '${device.name} (${describeEnum(device.type)})'), SizedBox(height: 16), Text("Output devices", style: Theme.of(context).textTheme.titleLarge), for (var device - in devices.where((device) => device.isOutput)) - Text('${device.name} (${device.type.name})'), + in devices.where((device) => device.isOutput)) + Text( + '${device.name} (${describeEnum(device.type)})'), ], ); }, diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index df18b278d560a36e7f3a14674d34daf8f1cc64bb..fd0047db8e72416700951aba7f354d94ffca3519 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,11 @@ import FlutterMacOS import Foundation import audio_session -import just_audio +import audioplayers_darwin import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) - JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/example/ohos/.gitignore b/example/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f0364c33c52a9b8ce1b89ea309af9c8ea68f718d --- /dev/null +++ b/example/ohos/.gitignore @@ -0,0 +1,16 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +entry/libs/arm64-v8a/libapp.so +entry/libs/arm64-v8a/libflutter.so +entry/libs/arm64-v8a/libvmservice_snapshot.so +entry/src/main/resources/rawfile/flutter_assets/ +har/flutter.har diff --git a/example/ohos/AppScope/app.json5 b/example/ohos/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d9e702cfa854993c2b361b2cd0e52009024279f --- /dev/null +++ b/example/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.ryanheise.audio_session_example", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/example/ohos/AppScope/resources/base/element/string.json b/example/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..c8bd0f4ba010bdf428387199e2dfe4b500007194 --- /dev/null +++ b/example/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "audio_session_example" + } + ] +} diff --git a/example/ohos/AppScope/resources/base/media/app_icon.png b/example/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/example/ohos/build-profile.json5 b/example/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..1d12140d202702d7c73d64f1b291fe5c45a660ce --- /dev/null +++ b/example/ohos/build-profile.json5 @@ -0,0 +1,27 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/example/ohos/dta/icudtl.dat b/example/ohos/dta/icudtl.dat new file mode 100644 index 0000000000000000000000000000000000000000..d1f10917ab52e3ebd251abd7f5377d7196b80d67 Binary files /dev/null and b/example/ohos/dta/icudtl.dat differ diff --git a/example/ohos/entry/.gitignore b/example/ohos/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2795a1c5b1fe53659dd1b71d90ba0592eaf7e043 --- /dev/null +++ b/example/ohos/entry/.gitignore @@ -0,0 +1,7 @@ + +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/example/ohos/entry/build-profile.json5 b/example/ohos/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..633d360fbc91a3186a23b66ab71b27e5618944cb --- /dev/null +++ b/example/ohos/entry/build-profile.json5 @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/hvigorfile.ts b/example/ohos/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..894fc15c6b793f085e6c8506e43d719af658e8ff --- /dev/null +++ b/example/ohos/entry/hvigorfile.ts @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { hapTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/example/ohos/entry/libs/arm64-v8a/libc++_shared.so b/example/ohos/entry/libs/arm64-v8a/libc++_shared.so new file mode 100644 index 0000000000000000000000000000000000000000..831c9353702073d45889352a4dafb93103d67d20 Binary files /dev/null and b/example/ohos/entry/libs/arm64-v8a/libc++_shared.so differ diff --git a/example/ohos/entry/oh-package.json5 b/example/ohos/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..995d00f6cd5991dc7142bbe5ea53b264223579fa --- /dev/null +++ b/example/ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "audio_session": "file:../har/audio_session.har", + "audioplayers_ohos": "file:../har/audioplayers_ohos.har", + "path_provider_ohos": "file:../har/path_provider_ohos.har" + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..a00049282e93c69f1b3dcd987538024f47e40ace --- /dev/null +++ b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { FlutterAbility } from '@ohos/flutter_ohos' +import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'; +import FlutterEngine from '@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine'; + +export default class EntryAbility extends FlutterAbility { + configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + GeneratedPluginRegistrant.registerWith(flutterEngine) + } +} diff --git a/example/ohos/entry/src/main/ets/pages/Index.ets b/example/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1125f9fdd95f4310a182c1c9e3680f37f73686c9 --- /dev/null +++ b/example/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import common from '@ohos.app.ability.common'; +import { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/module.json5 b/example/ohos/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7bbf78b18f39991b1404061c7437538c7d532bb7 --- /dev/null +++ b/example/ohos/entry/src/main/module.json5 @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + {"name" : "ohos.permission.INTERNET"}, + ] + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/color.json b/example/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/string.json b/example/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..ef07f22b7976c9e7efdeae3e6e261311be32768f --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "audio_session_example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/media/icon.png b/example/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/example/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/example/ohos/entry/src/main/resources/en_US/element/string.json b/example/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..ef07f22b7976c9e7efdeae3e6e261311be32768f --- /dev/null +++ b/example/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "audio_session_example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/example/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..9ab4406e6f33d3450aa57f7e86340f8e1e5bf922 --- /dev/null +++ b/example/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "audio_session_example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..25d4c71ff3cd584f5d64f6f8c0ac864928c234c4 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(function () { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(function () { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(function () { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain',0, function () { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc' + let b = 'b' + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b) + expect(a).assertEqual(a) + }) + }) +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f4140030e65d20df6af30a6bf51e464dea8f8aa6 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ca645e6013cfce8e7dbb728313cb8840c4da660 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..cef0447cd2f137ef82d223ead2e156808878ab90 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..1def08f2e9dcbfa3454a07b7a3b82b173bb90d02 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/module.json5 b/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fab77ce2e0c61e3ad010bab5b27ccbd15f9a8c96 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/example/ohos/hvigor/hvigor-config.json5 b/example/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a1e72840d839cebea4520acb6d1e9afff8c3cf01 --- /dev/null +++ b/example/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/example/ohos/hvigorfile.ts b/example/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f2d2aafe6d6a3a71a9944ebd0c91fbc308ac9d1 --- /dev/null +++ b/example/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/example/ohos/oh-package.json5 b/example/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2625d60a47c24a08492eecc63e2538342ccfd168 --- /dev/null +++ b/example/ohos/oh-package.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.0", + "name": "apptemplate", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + }, + "devDependencies": { + "@ohos/hypium": "1.0.6" + }, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "audio_session": "file:./har/audio_session.har", + "@ohos/flutter_module": "file:./entry", + "audioplayers_ohos": "file:./har/audioplayers_ohos.har", + "path_provider_ohos": "file:./har/path_provider_ohos.har" + } +} \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fbb87e4f5a8100898eb572fa4734182e7c938327..709c8bb86253009ba104b3dea31cfb2b725e1c0e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -11,7 +11,11 @@ environment: dependencies: flutter: sdk: flutter - just_audio: ^0.9.31 + # just_audio: ^0.9.31 + audioplayers: + git: + url: https://gitee.com/openharmony-sig/flutter_audioplayers.git + path: packages/audioplayers audio_session: path: ../ diff --git a/lib/src/core.dart b/lib/src/core.dart index abce49ad286157827b2c1d0cc476ff51384acd8e..4813c8c36248a303f210ae974b1a940933b43f9f 100644 --- a/lib/src/core.dart +++ b/lib/src/core.dart @@ -8,6 +8,7 @@ import 'package:rxdart/rxdart.dart'; import 'android.dart'; import 'darwin.dart'; +import 'ohos.dart'; /// Manages a single audio session to be used across different audio plugins in /// your app. [AudioSession] will configure your app by describing to the operating @@ -47,6 +48,10 @@ class AudioSession { final _androidAudioManager = !kIsWeb && Platform.isAndroid ? AndroidAudioManager() : null; final _avAudioSession = !kIsWeb && Platform.isIOS ? AVAudioSession() : null; + final _ohosAudionManager = + !kIsWeb && defaultTargetPlatform == TargetPlatform.ohos + ? OhosAudioManager() + : null; AudioSessionConfiguration? _configuration; final _configurationSubject = BehaviorSubject(); final _interruptionEventSubject = PublishSubject(); @@ -147,6 +152,24 @@ class AudioSession { _devicesSubject.add(await getDevices()); } }); + _ohosAudionManager?.setAudioDevicesAddedListener((devices) async { + _devicesChangedEventSubject.add(AudioDevicesChangedEvent( + devicesAdded: devices.map(_ohosDevice2device).toSet(), + devicesRemoved: {}, + )); + if (_devicesSubject.hasListener) { + _devicesSubject.add(await getDevices()); + } + }); + _ohosAudionManager?.setAudioDevicesRemovedListener((devices) async { + _devicesChangedEventSubject.add(AudioDevicesChangedEvent( + devicesAdded: {}, + devicesRemoved: devices.map(_ohosDevice2device).toSet(), + )); + if (_devicesSubject.hasListener) { + _devicesSubject.add(await getDevices()); + } + }); _channel.setMethodCallHandler((MethodCall call) async { final args = call.arguments as List?; switch (call.method) { @@ -228,6 +251,8 @@ class AudioSession { bool? androidWillPauseWhenDucked, AudioSessionConfiguration fallbackConfiguration = const AudioSessionConfiguration.music(), + AudioConcurrencyMode? ohosAudioConcurrencyMode, + OhosAudioAttributes? ohosAudioAttributes, }) async { final configuration = _configuration ?? fallbackConfiguration; if (!isConfigured) { @@ -290,6 +315,29 @@ class AudioSession { final success = await _androidAudioManager!.abandonAudioFocus(); return success; } + } else if (!kIsWeb && defaultTargetPlatform == TargetPlatform.ohos) { + AudioConcurrencyMode concurrencyMode = ohosAudioConcurrencyMode ?? + AudioConcurrencyMode.concurrencyPauseOthers; + await _ohosAudionManager! + .setActive(active, concurrencyMode: concurrencyMode); + final success = await _ohosAudionManager!.setInterruptionEventListener( + OhosInterruptListernerRequest( + audioAttributes: + ohosAudioAttributes ?? configuration.ohosAudioAttributes, + onAudioFocusChanged: (reason) { + switch (reason) { + case AudioSessionDeactivatedReason.deactivatedLowerPriority: + _interruptionEventSubject.add(AudioInterruptionEvent( + true, AudioInterruptionType.pause)); + break; + case AudioSessionDeactivatedReason.deactivatedTimeout: + _interruptionEventSubject.add(AudioInterruptionEvent( + false, AudioInterruptionType.unknown)); + break; + } + }, + isOnListener: active)); + return success; } return true; } @@ -328,6 +376,12 @@ class AudioSession { outputPorts: currentRoute.outputs, ))); } + } else if (_ohosAudionManager != null) { + var flags = DevicesFlags.noneDevicesFlag; + if (includeInputs) flags |= DevicesFlags.inputDevicesFlag; + if (includeOutputs) flags |= DevicesFlags.outputDevicesFlag; + final ohosDevices = await _ohosAudionManager!.getDevices(flags); + devices.addAll(ohosDevices.map(_ohosDevice2device).toSet()); } return devices; } @@ -462,6 +516,41 @@ class AudioSession { type: _androidType2type(device.type), ); } + + static AudioDeviceType _ohosType2type(DeviceType type) { + switch (type) { + case DeviceType.invalid: + return AudioDeviceType.unknown; + case DeviceType.earpiece: + return AudioDeviceType.builtInEarpiece; + case DeviceType.speaker: + return AudioDeviceType.builtInSpeaker; + case DeviceType.wiredHeadset: + return AudioDeviceType.wiredHeadset; + case DeviceType.wiredHeadphones: + return AudioDeviceType.wiredHeadphones; + case DeviceType.bluetoothSco: + return AudioDeviceType.bluetoothSco; + case DeviceType.bluetoothA2dp: + return AudioDeviceType.bluetoothA2dp; + case DeviceType.mic: + return AudioDeviceType.builtInMic; + case DeviceType.usbHeadset: + return AudioDeviceType.usbAudio; + case DeviceType.defaultDevice: + return AudioDeviceType.builtInSpeaker; + } + } + + static AudioDevice _ohosDevice2device(OhosAudioDeviceDescriptor device) { + return AudioDevice( + id: device.id.toString(), + name: device.displayName, + isInput: device.deviceRole == DeviceRole.inputDevice, + isOutput: device.deviceRole == DeviceRole.outputDevice, + type: _ohosType2type(device.deviceType), + ); + } } /// A configuration for [AudioSession] describing what type of audio your app @@ -482,6 +571,7 @@ class AudioSessionConfiguration { final AndroidAudioAttributes? androidAudioAttributes; final AndroidAudioFocusGainType androidAudioFocusGainType; final bool? androidWillPauseWhenDucked; + final OhosAudioAttributes? ohosAudioAttributes; /// Creates an audio session configuration from scratch. /// @@ -507,6 +597,7 @@ class AudioSessionConfiguration { this.androidAudioAttributes, this.androidAudioFocusGainType = AndroidAudioFocusGainType.gain, this.androidWillPauseWhenDucked, + this.ohosAudioAttributes, }); AudioSessionConfiguration.fromJson(Map data) diff --git a/lib/src/ohos.dart b/lib/src/ohos.dart new file mode 100644 index 0000000000000000000000000000000000000000..faa86038619dc1adcdc6914d111f1ce69e604fd2 --- /dev/null +++ b/lib/src/ohos.dart @@ -0,0 +1,598 @@ +// +// Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'dart:async'; + +import 'package:audio_session/src/util.dart'; +import 'package:flutter/services.dart'; + +class OhosAudioManager { + static const MethodChannel _channel = + MethodChannel('com.ryanheise.ohos_audio_manager'); + static OhosAudioManager? _instance; + + OhosOnAudioDevicesChanged? _onAudioDevicesAdded; + OhosOnAudioDevicesChanged? _onAudioDevicesRemoved; + OhosOnAudioFocusChanged? _onAudioFocusChanged; + + factory OhosAudioManager() { + return _instance ??= OhosAudioManager._(); + } + + void setAudioDevicesAddedListener(OhosOnAudioDevicesChanged listener) { + _onAudioDevicesAdded = listener; + } + + void setAudioDevicesRemovedListener(OhosOnAudioDevicesChanged listener) { + _onAudioDevicesRemoved = listener; + } + + OhosAudioManager._() { + _channel.setMethodCallHandler((MethodCall call) async { + final args = call.arguments as List; + switch (call.method) { + case 'onAudioDevicesAdded': + if (_onAudioDevicesAdded != null) { + _onAudioDevicesAdded!(_decodeAudioDevices(args[0])); + } + break; + case 'onAudioDevicesRemoved': + if (_onAudioDevicesRemoved != null) { + _onAudioDevicesRemoved!(_decodeAudioDevices(args[0])); + } + break; + case 'onScoAudioStateUpdated': + break; + case 'onAudioInterrupt': + if (_onAudioFocusChanged != null) { + _onAudioFocusChanged!(decodeEnum( + AudioSessionDeactivatedReason.values, args[0] as int?, + defaultValue: + AudioSessionDeactivatedReason.deactivatedLowerPriority)); + } + break; + } + }); + } + + Future setInterruptionEventListener( + OhosInterruptListernerRequest listernerRequest) async { + _onAudioFocusChanged = listernerRequest.onAudioFocusChanged; + return (await _channel.invokeMethod( + 'setInterruptionEventListener', [listernerRequest.toJson()]))!; + } + + Future setActive(bool active, + {AudioConcurrencyMode? concurrencyMode}) async => + (await _channel + .invokeMethod('setActive', [active, concurrencyMode?.index]))!; + + Future isVolumeUnadjustable() async { + return (await _channel.invokeMethod('isVolumeUnadjustable'))!; + } + + Future getRingerMode() async { + return decodeEnum(AudioRingMode.values, + (await _channel.invokeMethod('getRingerMode')), + defaultValue: AudioRingMode.ringerModeNormal); + } + + Future getMaxVolume(AudioVolumeType streamType) async { + return (await _channel.invokeMethod( + 'getMaxVolume', streamType.value))!; + } + + Future getMinVolume(AudioVolumeType streamType) async { + return (await _channel.invokeMethod( + 'getMinVolume', streamType.value))!; + } + + Future getVolume(AudioVolumeType streamType) async { + return (await _channel.invokeMethod('getVolume', streamType.value))!; + } + + Future getSystemVolumeInDb(AudioVolumeType streamType) async { + return (await _channel.invokeMethod( + 'getSystemVolumeInDb', streamType.value))!; + } + + Future isMute(AudioVolumeType streamType) async { + return (await _channel.invokeMethod('isMute', streamType.value))!; + } + + Future> getDevices(DevicesFlags flags) async { + return _decodeAudioDevices( + (await _channel.invokeMethod('getDevices', [flags.value]))!); + } + + Future setSpeakerphoneOn(bool enabled) async { + await _channel.invokeMethod('setSpeakerphoneOn', [enabled]); + } + + Future isSpeakerphoneOn() async { + return (await _channel.invokeMethod('isSpeakerphoneOn'))!; + } + + Future setMicrophoneMute(bool enabled) async { + await _channel.invokeMethod('setMicrophoneMute', [enabled]); + } + + Future isMicrophoneMute() async { + return (await _channel.invokeMethod('isMicrophoneMute'))!; + } + + Future getAudioScene() async { + return decodeEnum( + AudioScene.values, (await _channel.invokeMethod('getAudioScene')), + defaultValue: AudioScene.audioSceneDefault); + } + + Future isMusicActive() async { + return (await _channel.invokeMethod('isMusicActive'))!; + } + + Future setAudioParameter(String parameters, String value) async { + await _channel.invokeMethod('setAudioParameter', [parameters, value]); + } + + Future getAudioParameter(String key) async { + return (await _channel.invokeMethod('getAudioParameter', [key]))!; + } + + List _decodeAudioDevices(dynamic rawList) { + return (rawList as List).map(_decodeAudioDevice).toList(); + } + + OhosAudioDeviceDescriptor _decodeAudioDevice(dynamic raw) { + return OhosAudioDeviceDescriptor( + deviceRole: DeviceRoleExtension.getEnum(raw['deviceRole'] as int), + deviceType: DeviceTypeExtension.getEnum(raw['deviceType'] as int), + id: raw['id'] as int, + name: raw['name'] as String, + address: raw['address'] as String, + sampleRates: (raw['sampleRates'] as List).cast(), + channelCounts: (raw['channelCounts'] as List).cast(), + channelMasks: (raw['channelMasks'] as List).cast(), + displayName: raw['displayName'] as String); + } +} + +typedef OhosOnAudioFocusChanged = void Function( + AudioSessionDeactivatedReason focus); +typedef OhosOnAudioDevicesChanged = void Function( + List devices); + +class OhosInterruptEvent { + final OhosInterruptType eventType; + final OhosInterruptForceType forceType; + final OhosInterruptHint hintType; + + OhosInterruptEvent({ + required this.eventType, + required this.forceType, + required this.hintType, + }); +} + +class OhosInterruptType { + static const interruptTypeBegin = OhosInterruptType._(1); + static const interruptTypeEnd = OhosInterruptType._(2); + static const values = { + 1: interruptTypeBegin, + 2: interruptTypeEnd, + }; + final int index; + const OhosInterruptType._(this.index); +} + +extension OhosInterruptTypeExtension on OhosInterruptType { + int get value => [1, 2][index]; +} + +enum OhosInterruptForceType { + interruptForce, + interruptShare, +} + +enum InterruptMode { + shareMode, + indepentMode, +} + +enum OhosInterruptHint { + interruptHintNone, + interruptHintResume, + interruptHintPause, + interruptHintStop, + interruptHintDuck, + interruptHintUnduck, +} + +enum AudioRingMode { + ringerModeSilent, + ringerModeVibrate, + ringerModeNormal, +} + +enum AudioScene { + audioSceneDefault, + audioSceneRinging, + audioScenePhoneCall, + audioSceneVoiceChat, +} + +enum AudioSessionDeactivatedReason { + deactivatedLowerPriority, + deactivatedTimeout +} + +class DevicesFlags { + static const DevicesFlags noneDevicesFlag = DevicesFlags(0); + static const DevicesFlags outputDevicesFlag = DevicesFlags(1); + static const DevicesFlags inputDevicesFlag = DevicesFlags(2); + static const DevicesFlags allDevicesFlag = DevicesFlags(3); + static const DevicesFlags distributedOutputDevicesFlag = DevicesFlags(4); + static const DevicesFlags distributedInputDevicesFlag = DevicesFlags(8); + static const DevicesFlags addDistributedDevicesFlag = DevicesFlags(12); + + final int value; + + const DevicesFlags(this.value); + + DevicesFlags operator |(DevicesFlags flag) => + DevicesFlags(value | flag.value); + + DevicesFlags operator &(DevicesFlags flag) => + DevicesFlags(value & flag.value); + + bool contains(DevicesFlags flags) => flags.value & value == flags.value; + + @override + bool operator ==(Object other) => + other is DevicesFlags && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +class OhosAudioDeviceDescriptor { + final DeviceRole deviceRole; + final DeviceType deviceType; + final int id; + final String name; + final String address; + final List sampleRates; + final List channelCounts; + final List channelMasks; + final String displayName; + final AudioEncodingType? encodingTypes; + + OhosAudioDeviceDescriptor({ + required this.deviceRole, + required this.deviceType, + required this.id, + required this.name, + required this.address, + required this.sampleRates, + required this.channelCounts, + required this.channelMasks, + required this.displayName, + this.encodingTypes, + }); +} + +class AudioEncodingType { + static const encodingTypeInvalid = AudioEncodingType._(-1); + static const encodingTypeRaw = AudioEncodingType._(0); + static const values = { + -1: encodingTypeInvalid, + 0: encodingTypeRaw, + }; + final int value; + const AudioEncodingType._(this.value); + @override + bool operator ==(Object other) => + other is AudioEncodingType && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +enum DeviceRole { + inputDevice, + outputDevice, +} + +extension DeviceRoleExtension on DeviceRole { + static DeviceRole getEnum(int value) { + switch (value) { + case 1: + return DeviceRole.inputDevice; + case 2: + return DeviceRole.outputDevice; + default: + return DeviceRole.inputDevice; + } + } +} + +enum DeviceType { + invalid, + earpiece, + speaker, + wiredHeadset, + wiredHeadphones, + bluetoothSco, + bluetoothA2dp, + mic, + usbHeadset, + defaultDevice, +} + +extension DeviceTypeExtension on DeviceType { + int get value => [0, 1, 2, 3, 4, 7, 8, 15, 22, 1000][index]; + static DeviceType getEnum(int value) { + switch (value) { + case 0: + return DeviceType.invalid; + case 1: + return DeviceType.earpiece; + case 2: + return DeviceType.speaker; + case 3: + return DeviceType.wiredHeadset; + case 4: + return DeviceType.wiredHeadphones; + case 7: + return DeviceType.bluetoothSco; + case 8: + return DeviceType.bluetoothA2dp; + case 15: + return DeviceType.mic; + case 22: + return DeviceType.usbHeadset; + case 1000: + return DeviceType.defaultDevice; + default: + return DeviceType.defaultDevice; + } + } +} + +enum AudioVolumeType { + voiceCall, + ringTone, + media, + alarm, + accessibility, + voiceAssistant, + ultraSonic, + all +} + +extension AudioVolumeTypeExtension on AudioVolumeType { + int get value => [0, 2, 3, 4, 5, 9, 10, 100][index]; +} + +class OhosAudioAttributes { + final StreamUsage streamUsage; + final AudioSamplingRate samplingRate; + final AudioChannel channels; + final AudioSampleFormat sampleFormat; + final AudioEncodingType encodingType; + + const OhosAudioAttributes({ + this.streamUsage = StreamUsage.unknown, + this.samplingRate = AudioSamplingRate.sampleRate48000, + this.channels = AudioChannel.channel2, + this.sampleFormat = AudioSampleFormat.sampleFormatS16LE, + this.encodingType = AudioEncodingType.encodingTypeRaw, + }); + + OhosAudioAttributes.fromJson(Map data) + : this( + streamUsage: decodeMapEnum( + StreamUsage.values, data['streamUsage'] as int?, + defaultValue: StreamUsage.unknown), + samplingRate: decodeMapEnum( + AudioSamplingRate.values, data['samplingRate'] as int?, + defaultValue: AudioSamplingRate.sampleRate48000), + channels: decodeMapEnum( + AudioChannel.values, data['channels'] as int?, + defaultValue: AudioChannel.channel2), + sampleFormat: decodeMapEnum( + AudioSampleFormat.values, data['sampleFormat'] as int?, + defaultValue: AudioSampleFormat.sampleFormatS16LE), + encodingType: decodeMapEnum( + AudioEncodingType.values, data['encodingType'] as int?, + defaultValue: AudioEncodingType.encodingTypeRaw)); + + Map toJson() => { + 'streamUsage': streamUsage.value, + 'samplingRate': samplingRate.value, + 'channels': channels.value, + 'sampleFormat': sampleFormat.value, + 'encodingType': encodingType.value, + }; +} + +class StreamUsage { + static const unknown = StreamUsage._(0); + static const music = StreamUsage._(1); + static const voiceCommunication = StreamUsage._(2); + static const voiceAssistant = StreamUsage._(3); + static const alarm = StreamUsage._(4); + static const voiceMessage = StreamUsage._(5); + static const ringTone = StreamUsage._(6); + static const notification = StreamUsage._(7); + static const accessibility = StreamUsage._(8); + static const movie = StreamUsage._(10); + static const game = StreamUsage._(11); + static const audioBook = StreamUsage._(12); + static const navigation = StreamUsage._(13); + static const values = { + 0: unknown, + 1: music, + 2: voiceCommunication, + 3: voiceAssistant, + 4: alarm, + 5: voiceMessage, + 6: ringTone, + 7: notification, + 8: accessibility, + 10: movie, + 11: game, + 12: audioBook, + 13: navigation, + }; + final int value; + const StreamUsage._(this.value); + @override + bool operator ==(Object other) => + other is StreamUsage && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +class OhosInterruptListernerRequest { + final OhosAudioAttributes? audioAttributes; + final OhosOnAudioFocusChanged onAudioFocusChanged; + final bool isOnListener; + + const OhosInterruptListernerRequest({ + this.audioAttributes, + required this.onAudioFocusChanged, + required this.isOnListener, + }); + Map toJson() => { + 'audioAttributes': audioAttributes?.toJson(), + 'isOnListener': isOnListener, + }; +} + +class AudioSamplingRate { + static const sampleRate8000 = AudioSamplingRate._(8000); + static const sampleRate11025 = AudioSamplingRate._(11024); + static const sampleRate12000 = AudioSamplingRate._(12000); + static const sampleRate16000 = AudioSamplingRate._(16000); + static const sampleRate22050 = AudioSamplingRate._(22050); + static const sampleRate24000 = AudioSamplingRate._(24000); + static const sampleRate32000 = AudioSamplingRate._(32000); + static const sampleRate44100 = AudioSamplingRate._(44100); + static const sampleRate48000 = AudioSamplingRate._(48000); + static const sampleRate64000 = AudioSamplingRate._(64000); + static const sampleRate96000 = AudioSamplingRate._(96000); + + static const values = { + 8000: sampleRate8000, + 11024: sampleRate11025, + 12000: sampleRate12000, + 16000: sampleRate16000, + 22050: sampleRate22050, + 24000: sampleRate24000, + 32000: sampleRate32000, + 44100: sampleRate44100, + 48000: sampleRate48000, + 64000: sampleRate64000, + 96000: sampleRate96000, + }; + final int value; + const AudioSamplingRate._(this.value); + @override + bool operator ==(Object other) => + other is AudioSamplingRate && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +class AudioChannel { + static const channel1 = AudioChannel._(1); + static const channel2 = AudioChannel._(2); + static const channel3 = AudioChannel._(3); + static const channel4 = AudioChannel._(4); + static const channel5 = AudioChannel._(5); + static const channel6 = AudioChannel._(6); + static const channel7 = AudioChannel._(7); + static const channel8 = AudioChannel._(8); + static const channel9 = AudioChannel._(9); + static const channel10 = AudioChannel._(10); + static const channel11 = AudioChannel._(11); + static const channel12 = AudioChannel._(12); + static const channel13 = AudioChannel._(13); + static const channel14 = AudioChannel._(14); + static const channel15 = AudioChannel._(15); + static const channel16 = AudioChannel._(16); + static const values = { + 1: channel1, + 2: channel2, + 3: channel3, + 4: channel4, + 5: channel5, + 6: channel6, + 7: channel7, + 8: channel8, + 9: channel9, + 10: channel10, + 11: channel11, + 12: channel12, + 13: channel13, + 14: channel14, + 15: channel15, + 16: channel16, + }; + final int value; + const AudioChannel._(this.value); + + @override + bool operator ==(Object other) => + other is AudioChannel && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +class AudioSampleFormat { + static const sampleFormatInvalid = AudioSampleFormat._(-1); + static const sampleFormatU8 = AudioSampleFormat._(0); + static const sampleFormatS16LE = AudioSampleFormat._(1); + static const sampleFormatS24LE = AudioSampleFormat._(2); + static const sampleFormatS32LE = AudioSampleFormat._(3); + static const sampleFormatF32LE = AudioSampleFormat._(4); + static const values = { + -1: sampleFormatInvalid, + 0: sampleFormatU8, + 1: sampleFormatS16LE, + 2: sampleFormatS24LE, + 3: sampleFormatS32LE, + 4: sampleFormatF32LE, + }; + final int value; + const AudioSampleFormat._(this.value); + @override + bool operator ==(Object other) => + other is AudioSampleFormat && value == other.value; + + @override + int get hashCode => value.hashCode; +} + +enum AudioConcurrencyMode { + concurrencyDefault, + concurrencyMixWithOthers, + concurrencyDuckOthers, + concurrencyPauseOthers, +} \ No newline at end of file diff --git a/ohos/.gitignore b/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/ohos/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/ohos/build-profile.json5 b/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b5cf54176f8d80660aec9b1757d42ce5b50b2267 --- /dev/null +++ b/ohos/build-profile.json5 @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + } + ] +} diff --git a/ohos/hvigorfile.ts b/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..5cdfdb9dcf894f0a1c8e3e6bac99341cdb273f27 --- /dev/null +++ b/ohos/hvigorfile.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/ohos/index.ets b/ohos/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..50548b8c1c2d6822d981d7ccb0332abfe781a348 --- /dev/null +++ b/ohos/index.ets @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import AudioSessionPlugin from './src/main/ets/components/plugin/AudioSessionPlugin'; +export default AudioSessionPlugin; diff --git a/ohos/oh-package.json5 b/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bf7909c59a69c4fc2b57078b9e180feea277c3c2 --- /dev/null +++ b/ohos/oh-package.json5 @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +{ + "name": "audio_session", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + } +} diff --git a/ohos/src/main/ets/components/plugin/AudioSessionPlugin.ets b/ohos/src/main/ets/components/plugin/AudioSessionPlugin.ets new file mode 100644 index 0000000000000000000000000000000000000000..0ff7dfe5da719fa3cb1033183973ac495c848679 --- /dev/null +++ b/ohos/src/main/ets/components/plugin/AudioSessionPlugin.ets @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FlutterPlugin, FlutterPluginBinding } from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import MethodChannel, { MethodCallHandler, MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel'; +import MethodCall from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall'; +import OhosAudioManager from './OhosAudioManager'; +import { Log } from '@ohos/flutter_ohos'; + +/** AudioSessionPlugin **/ +export default class AudioSessionPlugin implements FlutterPlugin, MethodCallHandler { + private channel: MethodChannel | null = null; + private ohosAudioManager: OhosAudioManager | null = null; + private static instances: AudioSessionPlugin[] = []; + private static configuration: Map; + + constructor() { + } + + getUniqueClassName(): string { + return "AudioSessionPlugin" + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + this.channel = new MethodChannel(binding.getBinaryMessenger(), "com.ryanheise.audio_session"); + this.channel.setMethodCallHandler(this); + this.ohosAudioManager = new OhosAudioManager(binding.getApplicationContext(), binding.getBinaryMessenger()); + AudioSessionPlugin.instances.push(this); + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + if (this.channel != null) { + this.channel.setMethodCallHandler(null) + } + this.ohosAudioManager?.dispose(); + this.ohosAudioManager = null; + let index = AudioSessionPlugin.instances.indexOf(this); + AudioSessionPlugin.instances.splice(index, 1); + } + + onMethodCall(call: MethodCall, result: MethodResult): void { + Log.i("AudioSessionPlugin", "onMethodCall: " + call.method); + switch (call.method) { + case "setConfiguration": { + const args: Array = call.args as Array; + Log.i("AudioSessionPlugin", "setConfiguration: " + args[0]); + AudioSessionPlugin.configuration = args[0] as Map; + result.success(null); + this.invokeMethod("onConfigurationChanged", AudioSessionPlugin.configuration); + } + case "getConfiguration": { + result.success(AudioSessionPlugin.configuration); + break; + } + default: + result.notImplemented(); + break; + } + } + + private invokeMethod(method: string, ...args: Array): void { + for(let i = 0; i < AudioSessionPlugin.instances.length; i++) { + const list: ESObject[] = [...args] + AudioSessionPlugin.instances[i].channel?.invokeMethod(method, list); + } + } +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/plugin/OhosAudioManager.ets b/ohos/src/main/ets/components/plugin/OhosAudioManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..6a74d8fc715847c90c5afaec090ea4899dfa66e3 --- /dev/null +++ b/ohos/src/main/ets/components/plugin/OhosAudioManager.ets @@ -0,0 +1,571 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BinaryMessenger, Log, MethodCall } from '@ohos/flutter_ohos'; +import MethodChannel, { MethodCallHandler, MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel'; +import { Context } from '@kit.AbilityKit'; +import audio from '@ohos.multimedia.audio'; +import { ArrayList, List } from '@kit.ArkTS'; +import { call } from '@kit.TelephonyKit'; +import { webSocket } from '@kit.NetworkKit'; +import { BusinessError } from '@kit.BasicServicesKit'; + +const TAG = "OhosAudioManager"; + +export default class OhosAudioManager implements MethodCallHandler { + private messenger : BinaryMessenger | null; + public channel : MethodChannel | null; + private singleton : Singleton | null = null; + + constructor(applicationContext: Context, messenger: BinaryMessenger) { + if(this.singleton == null) { + this.singleton = new Singleton(applicationContext); + } + this.messenger = messenger; + this.channel = new MethodChannel(messenger, "com.ryanheise.ohos_audio_manager"); + this.singleton.add(this); + this.channel.setMethodCallHandler(this); + } + public onMethodCall(call: MethodCall, result: MethodResult): void { + try { + const args: Array = call.args as Array; + Log.i(TAG, "onMethodCall: " + call.method); + Log.i(TAG, "onMethodCall: args: " + JSON.stringify(args)); + switch (call.method) { + case "setActive": { + result.success(this.singleton?.setActive(args)); + break; + } + case "dispatchMediaKeyEvent": { + result.success(this.singleton?.dispatchMediaKeyEvent(call.args)); + break; + } + case "isVolumeUnadjustable": { + result.success(this.singleton?.isVolumeFixed()); + break; + } + case "adjustStreamVolume": { + result.success(this.singleton?.adjustStreamVolume(args[0] as number, + args[1] as number, args[2] as number)); + break; + } + case "adjustVolume": { + result.success(this.singleton?.adjustVolume(args[0] as number, args[1] as number)); + } + case "adjustSuggestedStreamVolume": { + result.success(this.singleton?.adjustSuggestedStreamVolume(args[0] as number, + args[1] as number, args[2] as number)); + break; + } + case "getRingerMode": { + result.success(this.singleton?.getRingerMode()); + break; + } + case "getMaxVolume": { + result.success(this.singleton?.getMaxVolume(args[0] as number)); + break; + } + case "getMinVolume": { + result.success(this.singleton?.getMinVolume(args[0] as number)); + break; + } + case "getVolume": { + result.success(this.singleton?.getVolume(args[0] as number)); + break; + } + case "getSystemVolumeInDb": { + result.success(this.singleton?.getSystemVolumeInDb(args[0] as number, + args[1] as number, args[2] as number)); + break; + } + case "setRingerMode": { + result.success(this.singleton?.setRingerMode(args[0] as number)); + break; + } + case "setStreamVolume": { + result.success(this.singleton?.setStreamVolume(args[0] as number, + args[1] as number, args[2] as number)); + break; + } + case "isMute": { + result.success(this.singleton?.isMute(args[0] as number)); + break; + } + case "getAvailableCommunicationDevices": { + result.success(this.singleton?.getAvailableCommunicationDevices()); + break; + } + case "setCommunicationDevice": { + result.success(this.singleton?.setCommunicationDevice(args[0] as number)); + break; + } + case "getCommunicationDevice": { + result.success(this.singleton?.getCommunicationDevice()); + break; + } + case "clearCommunicationDevice": { + result.success(this.singleton?.clearCommunicationDevice()); + break; + } + case "setSpeakerphoneOn": { + result.success(this.singleton?.setSpeakerphoneOn(args[0] as boolean)); + break; + } + case "isSpeakerphoneOn": { + result.success(this.singleton?.isSpeakerphoneOn()); + break; + } + case "setAllowedCapturePolicy": { + result.success(this.singleton?.setAllowedCapturePolicy(args[0] as number)); + break; + } + case "getAllowedCapturePolicy": { + result.success(this.singleton?.getAllowedCapturePolicy()); + break; + } + case "isBluetoothScoAvailableOffCall": { + result.success(this.singleton?.isBluetoothScoAvailableOffCall()); + break; + } + case "startBluetoothSco": { + result.success(this.singleton?.startBluetoothSco()); + break; + } + case "stopBluetoothSco": { + result.success(this.singleton?.stopBluetoothSco()); + break; + } + case "setBluetoothScoOn": { + result.success(this.singleton?.setBluetoothScoOn(args[0] as boolean)); + break; + } + case "isBluetoothScoOn": { + result.success(this.singleton?.isBluetoothScoOn()); + break; + } + case "setMicrophoneMute": { + result.success(this.singleton?.setMicrophoneMute(args[0] as boolean)); + break; + } + case "isMicrophoneMute": { + result.success(this.singleton?.isMicrophoneMute()); + break; + } + case "setMode": { + result.success(this.singleton?.setMode(args[0] as number)); + break; + } + case "getAudioScene": { + result.success(this.singleton?.getAudioScene()); + break; + } + case "isMusicActive": { + result.success(this.singleton?.isMusicActive()); + break; + } + case "generateAudioSessionId": { + result.success(this.singleton?.generateAudioSessionId()); + break; + } + case "setAudioParameter": { + result.success(this.singleton?.setAudioParameter(args[0] as string, args[1] as string)); + break; + } + case "getAudioParameter": { + result.success(this.singleton?.getAudioParameter(args[0] as string)); + break; + } + case "getProperty": { + result.success(this.singleton?.getProperty(args[0] as string)); + break; + } + case "getDevices": { + result.success(this.singleton?.getDevices(args[0] as number)); + break; + } + case "getMicrophones": { + result.success(this.singleton?.getMicrophones()); + break; + } + case "isHapticPlaybackSupported": { + result.success(this.singleton?.isHapticPlaybackSupported()); + break; + } + case "setInterruptionEventListener": { + this.singleton?.setInterruptionEventListener(args[0] as Map, result); + break; + } + default: { + result.notImplemented(); + break; + } + } + } catch (err) { + result.error("Error: " + err, null, null); + } + } + + public dispose(): void { + this.channel?.setMethodCallHandler(null); + this.singleton?.remove(this); + if(this.singleton?.isEmpty()) { + this.singleton?.dispose(); + this.singleton = null; + } + this.channel = null; + this.messenger = null; + } + +} + +class Singleton { + private instances: OhosAudioManager[]; + private applicationContext: Context | null; + private audioManager: audio.AudioManager | null; + private audioDeviceDescriptors: audio.AudioDeviceDescriptors | undefined; + private audioActive: boolean = false; + private audioRender: audio.AudioRenderer | null = null; + + constructor(applicationContext: Context) { + this.instances = []; + this.applicationContext = applicationContext; + this.audioManager = audio.getAudioManager(); + this.audioDeviceDescriptors = undefined; + this.initAudioDeviceCallback(); + } + + private initAudioDeviceCallback(): void { + this.audioManager?.getRoutingManager().on("deviceChange", audio.DeviceFlag.ALL_DEVICES_FLAG, + (device: audio.DeviceChangeAction) => { + if(device.type == audio.DeviceChangeType.CONNECT) { + this.invokeMethod("onAudioDevicesAdded", this.encodeAudioDevices(device.deviceDescriptors)); + } else if(device.type == audio.DeviceChangeType.DISCONNECT) { + this.invokeMethod("onAudioDevicesRemoved", this.encodeAudioDevices(device.deviceDescriptors)); + } + + }); + } + + private encodeAudioDevices(devices: audio.AudioDeviceDescriptors): Map[] { + let result: Map[] = []; + for(let i = 0; i < devices.length; i++) { + result.push(this.encodeAudioDevice(devices[i])); + } + return result; + } + + private encodeAudioDevice(device: audio.AudioDeviceDescriptor): Map { + let deviceMap: Map = new Map(); + deviceMap.set("deviceRole", device.deviceRole); + deviceMap.set("deviceType", device.deviceType); + deviceMap.set("id", device.id); + deviceMap.set("name", device.name); + deviceMap.set("address", device.address); + deviceMap.set("sampleRates", device.sampleRates); + deviceMap.set("channelMasks", device.channelMasks); + deviceMap.set("displayName", device.displayName); + deviceMap.set("channelIndexMasks", device.channelMasks); + deviceMap.set("channelCounts", device.channelCounts); + return deviceMap; + } + + private decodeAudioRendererOptions(attributes: Map): audio.AudioRendererOptions | null { + if(!attributes.has("sampleRate") || !attributes.has("channels") || !attributes.has("sampleFormat") + || !attributes.has("encodingType") || !attributes.has("streamUsage")) { + return null; + } + let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: attributes.get("sampleRate") as audio.AudioSamplingRate, + channels: attributes.get("channels") as audio.AudioChannel, + sampleFormat: attributes.get("sampleFormat") as audio.AudioSampleFormat, + encodingType: attributes.get("encodingType") as audio.AudioEncodingType, + }; + let audioRendererInfo: audio.AudioRendererInfo = { + usage: attributes.get("streamUsage") as audio.StreamUsage, + rendererFlags: 0, + }; + let audioRendererOptions: audio.AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo, + }; + return audioRendererOptions; + } + + public add(manager: OhosAudioManager): void { + this.instances.push(manager); + } + public remove(manager: OhosAudioManager): void { + const index = this.instances.indexOf(manager); + if(index != -1) { + this.instances.splice(index, 1); + } + } + + public isEmpty() { + return this.instances.length == 0; + } + + public dispose(): void { + this.disposeAudioDeviceCallback(); + this.applicationContext = null; + this.audioManager = null; + } + + public async setInterruptionEventListener(args: Map, result: MethodResult): Promise { + let audioRendererOptions: audio.AudioRendererOptions | null = null; + audioRendererOptions = this.decodeAudioRendererOptions(args); + if(audioRendererOptions == null) { + let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, + channels: audio.AudioChannel.CHANNEL_1, + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, + }; + let audioRendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC, + rendererFlags: 0, + }; + audioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo, + }; + } + try { + this.audioRender = await audio.createAudioRenderer(audioRendererOptions); + this.audioRender?.on("audioInterrupt",(event: audio.InterruptEvent) => { + this.invokeMethod("onAudioInterrupt", event.eventType); + }) + } catch (err) { + Log.e(TAG, " set audioInterrupt err:" + JSON.stringify(err)); + } + result.success(true); + return true; + } + + private disposeAudioDeviceCallback(): void { + this.audioManager?.getRoutingManager().off("deviceChange"); + } + + private invokeMethod(method: string, ...args: Array): void { + for(let i = 0; i < this.instances.length; i++) { + const list: ESObject[] = [...args] + this.instances[i].channel?.invokeMethod(method, list); + } + } + + public setActive(args: Array): boolean { + this.audioActive = args[0] as boolean; + return true; + } + + public dispatchMediaKeyEvent(rawKeyEvent: ESObject): ESObject { + return null; + } + + public isVolumeFixed(): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .isVolumeUnadjustable(); + } + + public adjustStreamVolume(streamType: number, direction: number, flags: number): ESObject { + //TODO: AudioVolumeGroupManager.adjustSystemVolumeByStep + return null; + } + + public adjustVolume(direction: number, flags: number): ESObject { + //TODO: AudioVolumeGroupManager.setVolume + return null; + } + + public adjustSuggestedStreamVolume(direction: number, suggestedStreamType: number, flags: number): ESObject { + //TODO: no ohos api + return null; + } + + public getRingerMode(): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .getRingerModeSync(); + } + + public getMaxVolume(streamType: number): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .getMaxVolumeSync(streamType); + } + + public getMinVolume(streamType: number): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .getMinVolumeSync(streamType); + } + + public getVolume(streamType: number): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .getVolumeSync(streamType); + } + + public getSystemVolumeInDb(streamType: number, index: number, deviceType: number): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .getSystemVolumeInDbSync(streamType, index, deviceType); + } + + public setRingerMode(ringerMode: number): ESObject { + //TODO: setRingerMode + return null; + } + + public setStreamVolume(streamType: number, index: number, flags: number): ESObject { + //TODO: setVolume + return null; + } + + public isMute(streamType: number): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .isMuteSync(streamType); + } + + public getAvailableCommunicationDevices():Map[] { + this.audioDeviceDescriptors = this.audioManager?.getRoutingManager().getDevicesSync(audio.DeviceFlag.OUTPUT_DEVICES_FLAG); + let result: Map[] = []; + if(this.audioDeviceDescriptors != undefined) { + for(let i = 0; i < this.audioDeviceDescriptors?.length; i++) { + result.push(this.encodeAudioDevice(this.audioDeviceDescriptors[i])); + } + } + return result + } + + public setCommunicationDevice(deviceId: number): boolean { + this.audioDeviceDescriptors?.forEach(device =>{ + if(device.id == deviceId) { + //TODO: setCommunicationDevice + } + }) + return false; + } + + public getCommunicationDevice(): Map { + //TODO: getCommunicationDevice + return new Map(); + } + + public clearCommunicationDevice(): ESObject { + //TODO: clearCommunicationDevice + return null; + } + + public setSpeakerphoneOn(enabled: boolean): ESObject { + this.audioManager?.getRoutingManager().setCommunicationDevice(audio.CommunicationDeviceType.SPEAKER, enabled); + return null; + } + + public isSpeakerphoneOn(): ESObject { + return this.audioManager?.getRoutingManager().isCommunicationDeviceActiveSync(audio.CommunicationDeviceType.SPEAKER); + } + + public setAllowedCapturePolicy(capturePolicy: number): ESObject { + //TODO: setAllowedCapturePolicy + return null; + } + + public getAllowedCapturePolicy(): ESObject { + //TODO: getAllowedCapturePolicy + return null; + } + + public isBluetoothScoAvailableOffCall(): ESObject { + //TODO: isBluetoothScoAvailableOffCall + } + + public startBluetoothSco(): ESObject { + //TODO: startBluetoothSco + } + + public stopBluetoothSco(): ESObject { + //TODO: stopBluetoothSco + } + + public setBluetoothScoOn(enabled: boolean): ESObject { + //TODO: setBluetoothScoOn + } + + public isBluetoothScoOn(): ESObject { + //TODO: isBluetoothScoOn + } + + public setMicrophoneMute(enabled: boolean): ESObject { + this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .setMicrophoneMute(enabled); + return null; + } + + public isMicrophoneMute(): ESObject { + return this.audioManager?.getVolumeManager().getVolumeGroupManagerSync(audio.DEFAULT_VOLUME_GROUP_ID) + .isMicrophoneMuteSync(); + } + + public setMode(mode: number): ESObject { + //TODO: audioManager.setAudioScene + return null; + } + + public getAudioScene(): ESObject { + return this.audioManager?.getAudioSceneSync(); + } + + public isMusicActive(): ESObject { + return this.audioManager?.getStreamManager().isActiveSync(audio.AudioVolumeType.MEDIA); + } + + public generateAudioSessionId(): ESObject { + //TODO: no oh api + } + + public setAudioParameter(key: string, value: string): ESObject { + this.audioManager?.setAudioParameter(key, value); + return null; + } + + public getAudioParameter(key: string): ESObject { + this.audioManager?.getAudioParameter(key); + } + + + public getProperty(arg: string): ESObject { + return null; + } + + public getDevices(flags: number): ESObject { + let result: Array> = []; + this.audioDeviceDescriptors = this.audioManager?.getRoutingManager().getDevicesSync(flags); + if(this.audioDeviceDescriptors != undefined) { + for(let i = 0; i < this.audioDeviceDescriptors?.length; i++) { + result.push(this.encodeAudioDevice(this.audioDeviceDescriptors[i])); + } + } + return result; + } + + public getMicrophones(): ESObject { + let result: ArrayList> = new ArrayList(); + this.audioManager?.getRoutingManager().getDevicesSync(audio.DeviceFlag.INPUT_DEVICES_FLAG).forEach(device => { + if(device.deviceType == audio.DeviceType.MIC) + result.add(this.encodeAudioDevice(device)); + }) + return result; + } + + public isHapticPlaybackSupported(): ESObject { + return false; + } +} \ No newline at end of file diff --git a/ohos/src/main/module.json5 b/ohos/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..33f159f26892f3b0df29f8a10c58cb054a0c71cc --- /dev/null +++ b/ohos/src/main/module.json5 @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "audio_session", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} diff --git a/ohos/src/main/resources/base/element/string.json b/ohos/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1e76de0c66777cfe83568615c5c2e68c61d23fed --- /dev/null +++ b/ohos/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from npm package" + } + ] +} diff --git a/ohos/src/main/resources/en_US/element/string.json b/ohos/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1e76de0c66777cfe83568615c5c2e68c61d23fed --- /dev/null +++ b/ohos/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from npm package" + } + ] +} diff --git a/ohos/src/main/resources/zh_CN/element/string.json b/ohos/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1e76de0c66777cfe83568615c5c2e68c61d23fed --- /dev/null +++ b/ohos/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from npm package" + } + ] +} diff --git a/ohos/src/test/List.test.ets b/ohos/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..93d49a34c7c9fdb9924b9077e21f174107983cae --- /dev/null +++ b/ohos/src/test/List.test.ets @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest() +} \ No newline at end of file diff --git a/ohos/src/test/LocalUnit.test.ets b/ohos/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..3059cb42d1c90d97e2d0ccd09a79666da2643edf --- /dev/null +++ b/ohos/src/test/LocalUnit.test.ets @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index c8d3af66ab83627ec14a77291f2754bfeeee51e8..9c516be40ecf5873e434a5852b780e2631533d97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,3 +36,6 @@ flutter: web: pluginClass: AudioSessionWeb fileName: audio_session_web.dart + ohos: + package: com.ryanheise.audio_session + pluginClass: AudioSessionPlugin