AVSession Kit:是系统提供的音视频管控服务,用于统一管理系统中所有音视频行为。通常,音视频后台播放需要接入AVSession。应用接入播控中心通常还可以用于开发分布式媒体会话投播、设备切换功能。
长时任务:支持在应用退至后台后,应用进程不被挂起,仍可以长时间播放音视频。
应用如果要实现后台播放或锁屏播放,需要同时满足:根据应用类型正确创建并接入AVSession;按需申请相应的长时任务避免应用进入挂起状态。其中,AVSession主要是提供后台播放控制,允许用户在后台控制音视频的播放、暂停、资源切换、进度控制、输出设备切换等播放控制功能;在后台播放音频使进程保活主要依赖对应的长时任务。
使用流程:
通常,音视频后台播放场景的实现依赖于播控中心,建议开发者参考播控中心接入流程正确完成播控中心的接入。
注意,应用需创建对应类型的AVSession。
参考示例:
async initAvsession() {
this.session = await AVSessionManager.createAVSession(this.context, 'SESSION_NAME', this.type);
this.startContinuousTask(this.uiAbilityContext);
this.setAVMetadata();
this.registerSessionListener();
this.session.activate();
} private registerSessionListener() {
this.session!.on('play', () => {
console.info(`on play , do pause task`);
this.controller.start();
});
this.session!.on('pause', () => {
this.controller.pause();
});
this.session!.on('stop', () => {
this.controller.stop();
});
} private setAVMetadata() {
let metadata: AVSessionManager.AVMetadata = {
assetId: '0',
title: 'TITLE',
mediaImage: 'IMAGE',
duration: 91000,
};
this.session!.setAVMetadata(metadata).then(() => {
console.info(`SetAVMetadata successfully`);
}).catch((err: BusinessError) => {
console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
});
} private setAVPlaybackState(currentState: AVSessionManager.AVPlaybackState) {
this.session!.setAVPlaybackState(currentState, (err) => {
if (err) {
console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
} else {
console.info(`SetAVPlaybackState successfully`);
}
});
} async destroySession() {
this.unRegisterSessionListener();
this.stopContinuousTask(this.uiAbilityContext);
this.session?.destroy();
} private unRegisterSessionListener() {
this.session!.off('play');
this.session!.off('pause');
this.session!.off('stop');
} "requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:reason_background",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
}
], startContinuousTask(context?: common.UIAbilityContext) {
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: context?.abilityInfo.bundleName,
abilityName: context?.abilityInfo.name
}
],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
};
try {
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
try {
let list: Array<string> = ['audioPlayback'];
backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj)
.then(() => {
console.info('Operation startBackgroundRunning succeeded');
})
.catch((error: BusinessError) => {
console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
});
} catch (error) {
console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
});
} catch (error) {
console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
} stopContinuousTask(context?: common.UIAbilityContext) {
backgroundTaskManager.stopBackgroundRunning(context).then(() => {
console.info(`AVPlayerDemo Succeeded in operationing stopBackgroundRunning.`);
}).catch((err: BusinessError) => {
console.error(`AVPlayerDemo Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
});
} import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent, common } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
@State elapsedTime: number = 0;
public updateTime: number = 0;
@State currentState: AVSessionManager.AVPlaybackState = {
state: AVSessionManager.PlaybackState.PLAYBACK_STATE_INITIAL, // 播放状态。
position: {
elapsedTime: 0, // 已经播放的位置,以ms为单位。
updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位。
},
};
public context: Context = this.getUIContext().getHostContext() as Context;
public uiAbilityContext: common.UIAbilityContext | undefined = AppStorage.get('mUIAbilityContext');
public type: AVSessionManager.AVSessionType = 'audio';
public session: AVSessionManager.AVSession | undefined = undefined;
public controller: VideoController = new VideoController();
aboutToAppear(): void {
this.initAvsession();
}
aboutToDisappear(): void {
this.destroySession();
}
async initAvsession() {
this.session = await AVSessionManager.createAVSession(this.context, 'SESSION_NAME', this.type);
this.startContinuousTask(this.uiAbilityContext);
this.setAVMetadata();
this.registerSessionListener();
this.session.activate();
}
async destroySession() {
this.unRegisterSessionListener();
this.stopContinuousTask(this.uiAbilityContext);
this.session?.destroy();
}
private registerSessionListener() {
this.session!.on('play', () => {
console.info(`on play , do pause task`);
this.controller.start();
});
this.session!.on('pause', () => {
this.controller.pause();
});
this.session!.on('stop', () => {
this.controller.stop();
});
}
private unRegisterSessionListener() {
this.session!.off('play');
this.session!.off('pause');
this.session!.off('stop');
}
private setAVPlaybackState(currentState: AVSessionManager.AVPlaybackState) {
this.session!.setAVPlaybackState(currentState, (err) => {
if (err) {
console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
} else {
console.info(`SetAVPlaybackState successfully`);
}
});
}
private setAVMetadata() {
let metadata: AVSessionManager.AVMetadata = {
assetId: '0',
title: 'TITLE',
mediaImage: 'IMAGE',
duration: 91000,
};
this.session!.setAVMetadata(metadata).then(() => {
console.info(`SetAVMetadata successfully`);
}).catch((err: BusinessError) => {
console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
});
}
startContinuousTask(context?: common.UIAbilityContext) {
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: context?.abilityInfo.bundleName,
abilityName: context?.abilityInfo.name
}
],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
};
try {
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
try {
let list: Array<string> = ['audioPlayback'];
backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj)
.then(() => {
console.info('Operation startBackgroundRunning succeeded');
})
.catch((error: BusinessError) => {
console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
});
} catch (error) {
console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
});
} catch (error) {
console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
}
stopContinuousTask(context?: common.UIAbilityContext) {
backgroundTaskManager.stopBackgroundRunning(context).then(() => {
console.info(`AVPlayerDemo Succeeded in operationing stopBackgroundRunning.`);
}).catch((err: BusinessError) => {
console.error(`AVPlayerDemo Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
});
}
build() {
Column() {
Video({
src: $rawfile('free.mp4'),
controller: this.controller,
})
.width('100%')
.height(300)
.onStart(() => {
console.info('onStart');
this.currentState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY;
this.updateTime = new Date().getTime();
this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
this.setAVPlaybackState(this.currentState);
this.startContinuousTask(this.uiAbilityContext);
})
.onPause(() => {
console.info('onPause');
this.currentState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE;
this.updateTime = new Date().getTime();
this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
this.setAVPlaybackState(this.currentState);
})
.onStop(() => {
console.info('onStop');
this.currentState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_STOP;
this.updateTime = new Date().getTime();
this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
this.setAVPlaybackState(this.currentState);
this.stopContinuousTask(this.uiAbilityContext);
})
.onSeeked((playbackInfo: PlaybackInfo) => {
console.info('onSeeked');
this.elapsedTime = playbackInfo.time * 1000;
this.updateTime = new Date().getTime();
this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
this.setAVPlaybackState(this.currentState);
})
.onUpdate((playbackInfo: PlaybackInfo) => {
console.info('onUpdate');
this.elapsedTime = playbackInfo.time * 1000;
this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
})
Text(`应用当前播放进度:${this.elapsedTime}ms`)
Text(`应用当前播放状态:${this.currentState.state}`)
}
.width('100%')
.height('100%')
}
} Q:应用后台切换音视频资源,是否需要取消和重新申请长时任务?
A:应用后台切换音频一般在数秒内完成,故不需要取消、重新申请后台长时任务和AVSession,直接在播放器内切换资源,并重新设置新的AVSession元数据信息并注意同步正确的播控状态即可。
但是由于系统对长时任务有管控,不允许创建长时任务但是未实际进行相应的长时任务业务,否则会取消相应的长时任务。故应用切歌需要尽量在1分钟内重新起播,不能一直处于暂停或停止状态。
Q:应用接入长时任务以及AVSession,切到后台无法播放音乐。
A:应用需检查业务逻辑查看长时任务是否被取消,导致应用切后台后无法播放音乐,可以在音乐播放时申请长时任务,达到默认支持后台播放的效果,在播放器释放时取消长时任务。
应用后台播放时若进行资源切换等操作,不需要重新申请长时任务,直接进行资源切换即可。
Q:长时任务是否有数量限制?
A:参考长时任务约束与限制。
从API version 21开始,支持一个UIAbility同一时刻申请多个长时任务,最多可申请10个,具体实现可参考startBackgroundRunning()。
对于API version 20及之前版本,一个UIAbility(FA模型则为ServiceAbility)同一时刻仅支持申请一个长时任务,即在一个长时任务结束后才能继续申请。如果一个应用同时需要申请多个长时任务,需要创建多个UIAbility。
如果一个应用创建了多个UIAbility,一个UIAbility申请长时任务后,整个应用下的所有进程均不会被挂起。
Q:音视频后台播放时,如何在后台显示播控中心组件?
A:媒体类应用需要正确接入播控中心,并保证创建'video'或'audio'类型的AVSession;正确设置元数据;至少创建一条播控中心控制命令,播控中心组件才可以正确显示。
其中,通知中心的播控页面需要相应的流起播时由播控中心发送相应通知得到,删除播控通知将收到播控中心的pause控制命令。
Q:后台播放是发长时任务的通知还是发播控中心的通知,删除相应的通知后会怎样?
A:从API version 20开始,申请AUDIO_PLAYBACK类型长时任务但不接入AVSession,申请长时任务成功后会在通知栏显示通知(延迟发送);接入AVSession后,后台任务模块不会发送通知栏通知,在应用音频流起播时由AVSession发送通知。
若删除AVSession的通知,应用会收到AVSession的pause控制命令,后台任务模块的通知将显示,若删除后台任务的通知,后台任务将停止。
对于API version 19及之前的版本,后台任务模块不会在通知栏显示通知,若接入AVSession,在应用音频流起播时,AVSession会发送通知。
Q:特殊场景不接入AVSession如何使用长时任务开发后台播放?
A:通常,应用后台播放需要同时接入AVSession和启动相应的长时任务。但在某些特殊场景下,如在后台进行导出媒体文件,使用三方投播组件在后台进行投播,应用不接入AVSession,此时可以创建MODE_SPECIAL_SCENARIO_PROCESSING类型的长时任务进行后台播放支持。
Q:接入AVSession和长时任务进行音视频后台播放开发时,AVSession的activate()和deactivate()是否会影响音视频后台播放?
A:不会,AVSession的激活和去激活只影响本地媒体播放时播控中心控制命令是否有效。AVSession去激活后,在本地媒体播放场景下,AVSession的控制命令将无法传至应用。音视频后台播放的后台保活主要依赖长时任务,要保障应用执行相应的业务,否则系统会对应用进行管控,即应用退至后台会被挂起。