鸿蒙app开发·后台长任务与本地媒体服务
①后台长时任务
通过backgroundTaskManager的startBackgroundRunning接口,通过ability上下文,任务类型(音视频、语音等)、定义动作属性(拉起的ability)开启后台任务,并用backgroundTaskManager.stopBackgroundRunning结束后台任务。
note:需配合本地媒体会话使用。
封装:
//后台任务,backgroundTask.ets
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { common, wantAgent, WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { AVPlayerVideo } from './AVPlayerVideo';
// 后台长时任务管理类
export class BackgroundTask {
// 上下文
private context: Context;
// 是否正在运行
private isRunning: boolean = false;
// 构造函数
constructor(context: Context) {
this.context = context;
}
// 后台任务开始
async startBackgroundTask() {
if (this.isRunning) {
console.log('后台任务已启动,无需重复启动');
return;
}
const cmm = this.context as common.UIAbilityContext
//通知参数,用于指定点击长时任务通知后跳转的界面
const wantAgentInfo: wantAgent.WantAgentInfo = {
// 添加需要被拉起应用的bundleName和abilityName、模块名
wants: [{
bundleName: cmm.abilityInfo.bundleName,
abilityName: cmm.abilityInfo.name,
moduleName: cmm.abilityInfo.moduleName,
}],
// 指定点击通知栏消息后的动作是拉起ability
actionType: wantAgent.OperationType.START_ABILITY, //abilityWantAgent.OperationType
// 私有属性
requestCode: 0,
// 动作的具体属性
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
};
//长时任务的类型,当前为音视频播放。
const taskType: backgroundTaskManager.BackgroundMode = backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK;
//通过wantAgent模块下getWantAgent方法获取WantAgent对象
const wantAgentOBJ: WantAgent = await wantAgent.getWantAgent(wantAgentInfo);
// 开始后台任务
backgroundTaskManager.startBackgroundRunning(this.context, taskType, wantAgentOBJ).then((result) => {
//成功的逻辑
this.isRunning = true;
}).catch((error: BusinessError) => {
//失败的逻辑
console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
this.isRunning = false;
});
}
// 后台任务结束
async stopBackgroundTask() {
await backgroundTaskManager.stopBackgroundRunning(this.context)
this.isRunning = false;
}
}
②本地媒体会话
通过avSession这个会话管理模块定义好会话名字、类型、上下文后,调用createAVSession方法创建会话,并使用activate激活会话。添加对应的监听回调后,状态栏的对应的功能图标会亮起,可在状态监听中通过avplayer来控制播放,实现控制效果。
封装(需在播放完成后暂时暂停会话)
//本地媒体会话管理器
//本地媒体会话管理器
// 使用案例
// const sessionManager = new SessionManager(player);
// await sessionManager.play(); // 自动激活并播放
// await sessionManager.pause(); // 暂停
// await sessionManager.stop(); // 停止播放
// await sessionManager.endSession(); // 销毁
export class SessionManager {
private context: Context; // 上下文对象,通常为 UIAbility 上下文
private session?: AVSessionManager.AVSession; // 媒体会话对象
private player?: AVPlayerVideo; // 媒体播放器实例
private playState: AVSessionManager.PlaybackState = AVSessionManager.PlaybackState.PLAYBACK_STATE_INITIAL; // 当前播放状态
private isRunning: boolean = false; // 会话是否已激活
private currentPostion: number = 0; // 当前播放进度(单位:毫秒)
private total: number = 100000000; // 媒体总时长(单位:毫秒)
// 播放下一首/上一首的回调
public onPlayNext?: () => void;
public onPlayPrevious?: () => void;
//元数据
private metaData: AVSessionManager.AVMetadata|undefined;
constructor(player: AVPlayerVideo, context?: Context) {
this.player = player;
this.context = context ?? getContext(this);
this.init(); // 初始化媒体会话
}
/**
* 初始化方法:创建媒体会话,设置元数据、状态、监听器等
*/
private async init() {
await this.createSession(); // 创建媒体会话
// await this.setMetadata(); // 设置媒体信息
await this.setPlaybackState(); // 初始化播放状态
this.setListenerForMesFromController(); // 设置控制器消息监听
this.setLaunchAbility(); // 设置拉起能力
}
/**
* 创建媒体会话
*/
private async createSession() {
const sessionType: AVSessionManager.AVSessionType = 'video';
const sessionName = 'SESSION_NAME';
this.session = await AVSessionManager.createAVSession(this.context, sessionName, sessionType);
console.info(`Session created: ID = ${this.session.sessionId}`);
}
/**
* 设置元数据(由外部传入)(媒体信息)
*/
public async setMetadata(metadata: AVSessionManager.AVMetadata) {
this.metaData = metadata;
await this.session?.setAVMetadata(metadata);
}
/**
* 设置当前播放状态到媒体会话(包含播放进度)
*/
private async setPlaybackState() {
const playbackState: AVSessionManager.AVPlaybackState = {
state: this.playState,
isFavorite: false,
speed: 1,
duration: this.total, // 单位:秒
activeItemId: 0,
position: {
elapsedTime: this.currentPostion, // 当前播放时间(秒)
updateTime: Date.now() // 更新时间戳(毫秒)
}
};
await this.session?.setAVPlaybackState(playbackState);
}
/**
* 当前进度由页面传递来,更新当前播放进度
*/
public updateCurrentPosition(time: number) {
this.currentPostion = time;
this.setPlaybackState();
}
/**
* 总进度由页面传递
*/
public updateTotalTime(time: number) {
this.total = time;
console.log('this is total:',this.total)
this.setPlaybackState();
// 更新元数据
this.metaData!.duration = time;
this.setMetadata(this.metaData!)
// 重置当前播放进度
this.currentPostion = 0;
}
/**
* 激活媒体会话(如果尚未激活)
*/
async activateSession() {
if (!this.isRunning && this.session) {
try {
await this.session.activate();
this.isRunning = true;
console.info('Session activated.');
} catch (err) {
console.error('Failed to activate session:', err);
}
}
}
/**
* 播放媒体
*/
async play() {
await this.activateSession();
this.player?.play();
this.playState = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY;
await this.setPlaybackState(); // 同步播放状态
}
/**
* 暂停播放
*/
async pause() {
this.player?.pause();
this.playState = AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE;
await this.setPlaybackState();
}
/**
* 停止播放
*/
async stop() {
this.player?.stop();
this.playState = AVSessionManager.PlaybackState.PLAYBACK_STATE_STOP;
await this.setPlaybackState();
}
/**
* 跳转到指定播放时间
*/
async seek(time: number) {
this.player?.avPlayerSeek(time);
this.playState = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY;
await this.setPlaybackState();
}
/**
* 控制器消息监听(来自系统或外设的控制行为)
*/
private setListenerForMesFromController() {
if (!this.session) {
return;
}
this.session.on('play', async () => {
console.info(`on play`);
await this.play();
console.info('this is op:',JSON.stringify(this.currentPostion))
});
this.session.on('pause', async () => {
console.info(`on pause`);
await this.pause();
});
this.session.on('stop', async () => {
console.info(`on stop`);
await this.stop();
});
this.session.on('seek', (time: number) => {
console.info(`[SessionManager] 接收到 seek 指令,时间点: ${time}`);
this.player?.avPlayerSeek(time); // 媒体跳转
});
// ✅ 新增:下一首
this.session.on('playNext', () => {
console.info('[SessionManager] 接收到 playNext 指令');
// 通知 UI 调用播放下一首
// 方式一:通过回调(推荐)
this.onPlayNext?.();
});
// ✅ 新增:上一首
this.session.on('playPrevious', () => {
console.info('[SessionManager] 接收到 playPrevious 指令');
this.onPlayPrevious?.();
});
// ✅ 新增:快进3秒
this.session.on('fastForward', () => {
let newTime = this.currentPostion + 3000;
this.player!.avPlayerSeek(newTime);
this.updateCurrentPosition(newTime);
});
// ✅ 新增:快退3秒
this.session.on('rewind', () => {
let newTime = this.currentPostion - 3000;
if (newTime < 0) newTime = 0;
this.player!.avPlayerSeek(newTime);
this.updateCurrentPosition(newTime);
});
// ✅ 新增:设置播放速度
this.session.on('setSpeed', (speed) => {
console.info(`on setSpeed , the speed is ${speed}`);
// do some tasks ···
});
// ✅ 新增:设置循环模式
this.session.on('setLoopMode', (mode) => {
console.info(`on setLoopMode , the loop mode is ${mode}`);
});
// ✅ 新增:切换收藏状态
this.session.on('toggleFavorite', (assetId) => {
console.info(`on toggleFavorite , the target asset Id is ${assetId}`);
});
}
/**
* 解绑控制器监听器(用于清理)
*/
async unsetListenerForMesFromController() {
this.session?.off('play');
this.session?.off('pause');
this.session?.off('stop');
this.session?.off('playNext');
this.session?.off('playPrevious');
this.session?.off('fastForward');
this.session?.off('rewind');
this.session?.off('seek');
this.session?.off('setSpeed');
this.session?.off('setLoopMode');
this.session?.off('toggleFavorite');
}
/**
* 设置会话拉起能力(锁屏或外设点击后拉起 App)
*/
private setLaunchAbility() {
if (!this.context) {
return;
}
const ctx = this.context as common.UIAbilityContext;
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [{
bundleName: ctx.abilityInfo.bundleName,
abilityName: ctx.abilityInfo.name,
moduleName: ctx.abilityInfo.moduleName,
}],
operationType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
wantAgent.getWantAgent(wantAgentInfo).then(agent => {
this.session?.setLaunchAbility(agent);
}).catch(() => {
AlertDialog.show({ message: '会话拉起窗口失败', alignment: DialogAlignment.Center });
});
}
/**
* 销毁媒体会话
*/
async endSession() {
await this.session?.destroy();
this.isRunning = false;
}
/**
* 禁用媒体会话
*/
async disableSession() {
await this.session?.deactivate();
this.isRunning = false;
}
}
视频播放与通知栏联动实现
// videoPlayerPage.ets
import { AVPlayerVideo } from './AVPlayerVideo';
import { BackgroundTask, SessionManager } from './backgroundTask';
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
@Entry
@Component
struct VideoPlayerPage {
private player: AVPlayerVideo = new AVPlayerVideo();
private xComponentController: XComponentController = new XComponentController();
//当前的播放进度
@State currentTime: number = 0;
//总播放时长
@State totalTime: number = 0;
//后台任务类
backgroundTask = new BackgroundTask(getContext(this));
//会话类,需要传入上下文与播放的控制类
sessionManager = new SessionManager(this.player, getContext(this));
// 作品信息列表
@State videoList: AVSessionManager.AVMetadata[] = [
{
assetId: "song001",
title: "晴天",
artist: "周杰伦",
subtitle: "叶惠美",
mediaImage: "https://i0.hdslb.com/bfs/archive/d2b0b68b1e0c45853db463a1e1c4f04002a102fc.jpg",
description: "经典情歌",
lyric: "[00:00.00]晴天 - 周杰伦\n[00:15.00]故事的小黄花 从出生那年就飘着..."
},
{
assetId: "song002",
title: "七里香",
artist: "周杰伦",
subtitle: "七里香",
mediaImage: "https://i0.hdslb.com/bfs/archive/d2b0b68b1e0c45853db463a1e1c4f04002a102fc.jpg",
description: "经典情歌",
lyric: "[00:00.00]七里香 - 周杰伦\n[00:15.00]故事的小黄花 从出生那年就飘着..."
}
];
// 相关作品地址
@State url:string[] = [
'https://consumer.huawei.com/content/dam/huawei-cbg-site/cn/mkt/pdp/phones/pura-x/video/kv-popup.mp4',
'https://v-cdn.zjol.com.cn/276985.mp4'
];
// 当前播放索引
@State currentIndex: number = 0;
// 加载当前播放视频(封装)
private async loadCurrentVideo(surfaceId: string) {
// 停止播放,并更新会话状态
this.player.stop();
// 加载当前播放视频
await this.player.init(surfaceId, this.url[this.currentIndex], true);
// 获取视频时长,并更新会话状态
this.player.getDuration((time) => {
this.totalTime = time;
this.sessionManager.updateTotalTime(time);
//更新元数据信息
if (this.videoList[this.currentIndex].duration === undefined) this.videoList[this.currentIndex].duration = this.totalTime;
});
// 获取当前播放进度,并更新会话状态
this.player.getCurrentPosition((time) => {
this.currentTime = time;
this.sessionManager.updateCurrentPosition(time);
});
console.log('元数据信息 :',JSON.stringify(this.videoList))
// 设置媒体元数据
await this.sessionManager.setMetadata(this.videoList[this.currentIndex]);
// 设置播放完成回调
this.player.setOnPlaybackCompleted(async () => {
// 播放完成逻辑区,播放下一首
this.playNext(this.xComponentController.getXComponentSurfaceId());
});
}
// 播放下一首
private async playNext(surfaceId: string) {
this.currentIndex = (this.currentIndex + 1) % this.videoList.length;
await this.loadCurrentVideo(surfaceId);
await this.sessionManager.pause()
}
// 播放上一首
private async playPrevious(surfaceId: string) {
this.currentIndex = (this.currentIndex - 1 + this.videoList.length) % this.videoList.length;
await this.loadCurrentVideo(surfaceId);
await this.sessionManager.pause()
}
build() {
Column({ space: 20 }) {
XComponent({
id: 'video_surface',
type: 'surface',
controller: this.xComponentController
})
.width('100%')
.height(300)
.onLoad(async () => {
// 获取 XComponent 的 Surface ID
const surfaceId = this.xComponentController.getXComponentSurfaceId();
// 加载当前播放视频
this.loadCurrentVideo(surfaceId)
// 注册回调给系统媒体控制栏用
this.sessionManager.onPlayNext = async () => {
await this.playNext(surfaceId);
};
this.sessionManager.onPlayPrevious = async () => {
await this.playPrevious(surfaceId);
};
});
Slider({
value: this.currentTime / 1000,
min: 0,
max: this.totalTime / 1000,
step: 1,
style: SliderStyle.OutSet // 设置样式为 OutSet(有凸起效果)
})
.blockColor('#4A90E2')// 滑块颜色
.trackColor('#E0E0E0')// 轨道背景色
.selectedColor('#4A90E2')// 已填充进度颜色
.onChange((value: number) => {
// 跳转到指定时间
this.player.avPlayerSeek(value * 1000)
// 更新会话状态
this.sessionManager.seek(value * 1000)
})
.width('90%')
Row({ space: 10 }) {
Button('播放')
.onClick(() => {
this.player.play();
// 启动后台任务
this.backgroundTask.startBackgroundTask()
// 启动本地媒体会话
this.sessionManager.play()
});
Button('暂停')
.onClick(() => {
// 暂停播放,并更新会话状态
this.player.pause();
this.sessionManager.pause()
});
Button('快进5秒')
.onClick(async () => {
this.player.avPlayerSeek(this.currentTime + 5000); // 跳转到5秒(也可以传 currentTime + 5)
});
}
Button('停止')
.backgroundColor('#ff4444')
.onClick(() => {
this.player.stop();
this.player.closeFile();
});
Button('下一个')
.onClick(() => {
this.playNext(this.xComponentController.getXComponentSurfaceId());
//
});
Button('上一个')
.onClick(() => {
this.playPrevious(this.xComponentController.getXComponentSurfaceId());
// 更新会话状态
});
}
.padding(20);
}
}
播放class如下
/// 封装 AVPlayer 播放器,用于视频播放功能
// 需配合组件 XComponent 使用
import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class AVPlayerVideo {
private surfaceID: string = '';
private isSeek: boolean = true;
private fileSize: number = -1;
private fd: number = 0;
private avPlayer: media.AVPlayer | undefined = undefined;
private file: fs.File | undefined;
public PathUrl: string = '';
// 播放完成回调
private playbackCompletedCallback: (() => void) | null = null;
// 设置播放完成回调
setOnPlaybackCompleted(callback: () => void) {
this.playbackCompletedCallback = callback;
}
// 初始化播放器,支持本地路径和网络 URL
async init(surfaceID: string, url: string, isSeek?: boolean) {
try {
if (this.avPlayer) {
this.avPlayer.release();
this.avPlayer = undefined;
}
this.avPlayer = await media.createAVPlayer();
this.surfaceID = surfaceID;
this.isSeek = isSeek ?? true;
if (url.startsWith('http')) {
this.PathUrl = url;
} else {
this.file = await fs.open(url, fs.OpenMode.READ_ONLY);
this.PathUrl = 'fd://' + this.file.fd;
}
this.avPlayer.url = this.PathUrl;
this.setAVPlayerCallback(this.avPlayer);
} catch (e) {
console.error('初始化播放器失败: ' + JSON.stringify(e));
}
}
// 设置 AVPlayer 的各种回调事件
private setAVPlayerCallback(avPlayer: media.AVPlayer) {
avPlayer.on('startRenderFrame', () => {
console.info('开始渲染视频帧');
});
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`定位完成,时间点: ${seekDoneTime}`);
});
avPlayer.on('error', async (err: BusinessError) => {
console.error(`AVPlayer 错误 [${err.code}]: ${err.message}`);
await avPlayer.reset();
await avPlayer.release();
});
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
console.info(`状态变更: ${state}, 原因: ${reason}`);
switch (state) {
case 'initialized':
avPlayer.surfaceId = this.surfaceID;
await avPlayer.prepare();
break;
case 'prepared':
break;
case 'completed':
// 播放完成回调
this.playbackCompletedCallback!();
break;
case 'stopped':
await avPlayer.reset();
await avPlayer.release();
break;
}
});
}
// 手动播放
public play() {
this.avPlayer?.play();
}
// 手动暂停
public pause() {
this.avPlayer?.pause();
}
// 手动停止并释放资源
public stop() {
this.avPlayer?.stop();
this.avPlayer?.release();
}
// 获取当前播放进度(回调)
public getCurrentPosition(callback: (time: number) => void) {
this.avPlayer?.on('timeUpdate', callback);
}
// 获取总时长(一次性回调)
public getDuration(callback: (time: number) => void) {
this.avPlayer?.on('durationUpdate', callback);
}
// 跳转播放位置(单位:秒)
async avPlayerSeek(time: number): Promise<void> {
if (!this.avPlayer) throw new Error('avPlayer 未初始化');
if (!this.isSeek) throw new Error('当前资源不支持 seek');
this.avPlayer.seek(time, media.SeekMode.SEEK_CLOSEST);
}
// 设置播放速度
public setSpeed(speed: number) {
this.avPlayer?.setSpeed(speed);
}
// 设置播放比特率,仅对网络流有效(HLS/DASH)
public setBitrate(bitrate: number) {
this.avPlayer?.setBitrate(bitrate);
}
// 设置音量(0.0 - 1.0)
public setVolume(volume: number) {
this.avPlayer?.setVolume(volume);
}
// 关闭本地文件句柄
public closeFile() {
if (this.file) {
fs.close(this.file);
}
}
// 示例:预下载设置(未实现)
async preDownloadDemo() {
try {
const source = media.createMediaSourceWithUrl("http://xxx", { "User-Agent": "User-Agent-Value" });
const strategy: media.PlaybackStrategy = {
preferredWidth: 1,
preferredHeight: 2,
preferredBufferDuration: 3,
preferredHdr: false
};
this.avPlayer?.setMediaSource(source, strategy);
} catch (e) {
console.error('预下载设置失败: ' + JSON.stringify(e));
}
}
}
后台长时任务(单独简单版)
/**
* @Author:
* @Date: 2025.11.01
* @Description: backgroundTask.ets后台任务
*/
import { bundleManager, wantAgent } from '@kit.AbilityKit'
import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
import common from '@ohos.app.ability.common';
class BackgroundRunningManager {
// 申请长时任务
async startBackgroundRunning(ctx: Context | undefined) {
const context = ctx ?? getContext(this) as common.UIAbilityContext;
// 获取 bundle 应用信息
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
// 通过wantAgent模块下getWantAgent方法获取WantAgent对象
const wantAgentObj = await wantAgent.getWantAgent({
// 添加需要被拉起应用的bundleName和abilityName
wants: [{ bundleName: bundleInfo.name, abilityName: "EntryAbility" }],
// 使用者自定义的一个私有值
requestCode: 0,
})
// 重点2: 创建后台任务
await backgroundTaskManager.startBackgroundRunning(context,
backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj)
}
// 停止后台任务
async stopBackgroundRunning() {
backgroundTaskManager.stopBackgroundRunning(getContext())
}
}
export const backgroundRunningManagerClass = new BackgroundRunningManager()
note:1、需要申请权限:"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
}
],
2、声明后台模式类型。
在module.json5配置文件中abilities下的backgroundModes字段里,为需要使用长时任务的UIAbility声明相应的长时任务类型,配置文件中填写长时任务类型的配置项。
"module": {
"abilities": [
{
"backgroundModes": [
// 长时任务类型的配置项
"audioRecording",
"bluetoothInteraction",
"audioPlayback"
]
}
],
// ...
}
浙公网安备 33010602011771号