网站尺寸自适应,网页制作与网站建设题,网站管理模板,广安建设局网站本文是系列文章#xff0c;其他文章见#xff1a;敲鸿蒙木鱼#xff0c;积____功德#x1f436;#x1f436;#x1f436;——鸿蒙元服务开发#xff1a;从入门到放弃(1)敲鸿蒙木鱼#xff0c;积____功德#x1f436;#x1f436;#x1f436;——鸿蒙元服务开发其他文章见敲鸿蒙木鱼积____功德——鸿蒙元服务开发从入门到放弃(1)敲鸿蒙木鱼积____功德——鸿蒙元服务开发从入门到放弃(2)
本文完整源码查看funny-widget
简介
因为工作需要准备开发元服务所以就想着搞一个电子木鱼的DEMO学习一下元服务以及桌面卡片的功能开发知识。
详细了解HarmonyOS的元服务可查看官方介绍。
涉及知识点 元服务开发流程 加载图片 播放音频 开发调试 组件代码在卡片和元服务间共享 数据在卡片和元服务间共享
应用内卡片开发
因为元服务卡片存在音频播放问题在咨询了官方技术支持后确定是无法播放的。
在官方文档中看到了使用call事件拉起指定UIAbility到后台。
因此使用了此方法进行音频播放功能验证。
卡片代码
Entry
Component
struct WidgetEventCallCard {LocalStorageProp(formId) formId: string 12400633174999288;build() {Column() {Row() {Column() {Button() {Text(playLocalSound).padding(16)}.onClick(() {console.info(click playLocalSound)postCardAction(this, {action: call,abilityName: WidgetEventCallEntryAbility, // 只能跳转到当前应用下的UIAbilityparams: {formId: 12400633174999288,method: playLocalSound // 在EntryAbility中调用的方法名}});console.info(after click playLocalSound)})Button() {Text(playOnlineSound).padding(16)}.onClick(() {console.info(click playOnlineSound)postCardAction(this, {action: call,abilityName: WidgetEventCallEntryAbility, // 只能跳转到当前应用下的UIAbilityparams: {formId: 12400633174999288,method: playOnlineSound // 在EntryAbility中调用的方法名}});console.info(after click playOnlineSound)})}}.width(100%).height(80%).justifyContent(FlexAlign.Center)}.width(100%).height(100%).alignItems(HorizontalAlign.Center)}
}卡片上添加了两个按钮分别用来测试本地音频播放和在线音频播放。
Entry代码
import { AbilityConstant, UIAbility, Want } from kit.AbilityKit;
import { promptAction, window } from kit.ArkUI;
import { BusinessError } from kit.BasicServicesKit;
import { rpc } from kit.IPCKit;
import { hilog } from kit.PerformanceAnalysisKit;
import { media } from kit.MediaKit;
import { AudioManager } from ../utils/AudioManager;const TAG: string WidgetEventCallEntryAbility;
const DOMAIN_NUMBER: number 0xFF00;
const CONST_NUMBER_1: number 1;
const CONST_NUMBER_2: number 2;class MyParcelable implements rpc.Parcelable {num: number;str: string;constructor(num: number, str: string) {this.num num;this.str str;}marshalling(messageSequence: rpc.MessageSequence): boolean {messageSequence.writeInt(this.num);messageSequence.writeString(this.str);return true;}unmarshalling(messageSequence: rpc.MessageSequence): boolean {this.num messageSequence.readInt();this.str messageSequence.readString();return true;}
}export default class WidgetEventCallEntryAbility extends UIAbility {// 如果UIAbility第一次启动在收到call事件后会触发onCreate生命周期回调onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {try {// 监听call事件所需的方法this.callee.on(playLocalSound, (data: rpc.MessageSequence) {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, playLocalSound param: ${JSON.stringify(data.readString())});AudioManager.shared.playSound()return new MyParcelable(CONST_NUMBER_1, aaa);});this.callee.on(playOnlineSound, (data: rpc.MessageSequence) {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, playOnlineSound param: ${JSON.stringify(data.readString())});AudioManager.shared.playOnlineSound()return new MyParcelable(CONST_NUMBER_1, aaa);});} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, Failed to register callee on. Cause: ${JSON.stringify(err as BusinessError)});}}// 进程退出时解除监听onDestroy(): void | Promisevoid {try {this.callee.off(playLocalSound);this.callee.off(playOnlineSound);} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, Failed to register callee off. Cause: ${JSON.stringify(err as BusinessError)});}}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, testTag, %{public}s, Ability onWindowStageCreate);windowStage.loadContent(pages/Index, (err) {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.);});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, testTag, %{public}s, Ability onWindowStageDestroy);}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, testTag, %{public}s, Ability onForeground);}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, testTag, %{public}s, Ability onBackground);}
}主要就是在onCreate方法中监听playLocalSound和playOnlineSound两个call事件。
AudioManager
import { media } from kit.MediaKit
import { BusinessError } from kit.BasicServicesKit
import { hilog } from kit.PerformanceAnalysisKit
import { common } from kit.AbilityKitexport class AudioManager {static shared new AudioManager()playSound() {this.log(AudioManager playSound)this.playLocalSound()}log(message: string) {hilog.info(0x0000, 音频, %{public}s, ${message});console.info([音频]${message});}isSeek: boolean falsecount: number 0setAVPlayerCallback(avPlayer: media.AVPlayer) {this.log(setAVPlayerCallback)// seek操作结果回调函数avPlayer.on(seekDone, (seekDoneTime: number) {this.log(AVPlayer seek succeeded, seek time is ${seekDoneTime})})// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程avPlayer.on(error, (err: BusinessError) {this.log(avPlayer on error, code is ${err.code}, message is ${err.message})avPlayer.reset(); // 调用reset重置资源触发idle状态})// 状态机变化回调函数avPlayer.on(stateChange, async (state: media.AVPlayerState, reason: media.StateChangeReason) {this.log(stateChange回调${state}${reason.toString()})switch (state) {case idle: // 成功调用reset接口后触发该状态机上报console.info(AVPlayer state idle called.);avPlayer.release(); // 调用release接口销毁实例对象break;case initialized: // avplayer 设置播放源后触发该状态上报console.info(AVPlayer state initialized called.);avPlayer.prepare();break;case prepared: // prepare调用成功后上报该状态机console.info(AVPlayer state prepared called.);avPlayer.play(); // 调用播放接口开始播放break;case playing: // play成功调用后触发该状态机上报console.info(AVPlayer state playing called.);if (this.count ! 0) {if (this.isSeek) {console.info(AVPlayer start to seek.);avPlayer.seek(avPlayer.duration); //seek到音频末尾} else {// 当播放模式不支持seek操作时继续播放到结尾console.info(AVPlayer wait to play end.);}} else {avPlayer.pause(); // 调用暂停接口暂停播放}this.count;break;case paused: // pause成功调用后触发该状态机上报console.info(AVPlayer state paused called.);avPlayer.play(); // 再次播放接口开始播放break;case completed: // 播放结束后触发该状态机上报console.info(AVPlayer state completed called.);avPlayer.stop(); //调用播放结束接口break;case stopped: // stop接口成功调用后触发该状态机上报console.info(AVPlayer state stopped called.);avPlayer.reset(); // 调用reset接口初始化avplayer状态break;case released:console.info(AVPlayer state released called.);break;case error:console.info(AVPlayer state error called.);break;default:console.info(AVPlayer state unknown called.);break;}})}async playLocalSound() {hilog.info(0x0000, 音频, %{public}s, playLocalSound);console.debug([音频]playLocalSound)try {// 创建avPlayer实例对象let avPlayer: media.AVPlayer await media.createAVPlayer();this.log(createAVPlayer success)// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);// 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址// 返回类型为{fd,offset,length},fd为HAP包fd地址offset为媒体资源偏移量length为播放长度let context getContext(this) as common.ExtensionContext;this.log(getContextcontext${context})// hilog.info(0x0000, 组件, %{public}s, playLocalSoundcontext扩展名${context.extensionAbilityInfo.name}});let fileDescriptor await context.resourceManager.getRawFd(dang.mp3);this.log(playLocalSoundfileDescriptor.length${fileDescriptor.length}})let avFileDescriptor: media.AVFileDescriptor { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };this.isSeek true; // 支持seek操作// 为fdSrc赋值触发initialized状态机上报avPlayer.fdSrc avFileDescriptor;} catch (e) {this.log(playLocalSound出错:${e.toString()})}}// 以下demo为通过url设置网络地址来实现播放直播码流的demoasync playOnlineSound() {this.log(playOnlineSound)// 创建avPlayer实例对象let avPlayer: media.AVPlayer await media.createAVPlayer();this.log(createAVPlayer success)// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);avPlayer.url https://clemmensen.top/static/muyu.mp3;}
}音频播放代码提供了本地和在线两个播放逻辑。
结论
期望在应用未运行的情况下用卡片call方法拉起App到后台并播放音频。
实测播放rawfile音频失败播放在线音频成功
向官方技术支持咨询
鸿蒙技术支持
开发者你好这边测试本地是有音频的敲击声啊159******50
要先杀掉应用再点正常场景是“应用未运行的情况下”点击卡片播放音频鸿蒙技术支持
确实是的正在内部分析中鸿蒙技术支持
开发者你好初步结论是 let context getContext(this) as common.ExtensionContext;
此场景中上述context获取不到导致不能读取rawfile文件159******50
这个我清楚日志就能看到的。问题是怎么解决呢有没有其他方案读取rawfile鸿蒙技术支持
当前卡片框架不支持获取context这个确认为当前规格159******50
“ let context getContext(this) as common.ExtensionContext;”这个已经是通过call事件拉起application走到application的代码里了不算卡片代码的运行环境吧鸿蒙技术支持
开发者你好通过call事件去拉起还是借助卡片的能力卡片里面本身就是受限功能总结与体会
在鸿蒙系统初期开发这类小众功能真的是遍地是坑。普通的应用界面因为有大量应用在开发所以坑填的相对快但也不少像元服务、卡片、音频这样的混合领域坑得数量可能超出你预期开发者在前期做开发计划时尽量保守一些不要脑袋一热用iOS/Android的开发经验来轻率定时间进度。