/*
* Copyright (c) 2025 Huawei Device 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 { avSession } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common, wantAgent } from '@kit.AbilityKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { media } from '@kit.MediaKit';
import { BackgroundTaskManager } from '../common/utils/BackgroundTaskManager';
import { VideoPlayerController } from './VideoPlayerController';
import Logger from '../common/utils/Logger';
const TAG = '[VideoSessionController]';
export class VideoSessionController {
public videoSession: avSession.AVSession;
private constructor(videoSession: avSession.AVSession) {
this.videoSession = videoSession;
}
/**
* Creates and activates a new AVSession for video playback.
* @param context The UIAbility context for session creation.
* @returns Promise<AVSessionController> Initialized session controller instance.
*/
static async create(context: common.UIAbilityContext) {
let videoSession: avSession.AVSession;
try {
videoSession = await avSession.createAVSession(context, 'VIDEO_SESSION', 'video');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `get videoSession failed, errCode = ${err.code}, errMessage = ${err.message}.`);
return;
}
// Set up a background task.
BackgroundTaskManager.startContinuousTask(backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, context);
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: context.abilityInfo.bundleName,
abilityName: context.abilityInfo.name
}
],
actionType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
let agent = wantAgent.getWantAgent(wantAgentInfo).catch((err: BusinessError) => {
Logger.error(TAG, `getWantAgent failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
videoSession.setLaunchAbility(agent).catch((err: BusinessError) => {
Logger.error(TAG, `setLaunchAbility failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
videoSession.activate().catch((err: BusinessError) => {
Logger.error(TAG, `videoSession activate failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
return new VideoSessionController(videoSession);
}
/**
* Sets metadata for the current video session.
* @param curSource Video data containing index, name and head image.
* @param duration Total duration of the video in milliseconds.
*/
public async setAVMetadata(assetId: string, duration: number) {
const uiContext: UIContext | undefined = AppStorage.get('uiContext');
let context: common.UIAbilityContext = uiContext?.getHostContext() as common.UIAbilityContext;
let metadata: avSession.AVMetadata;
try {
metadata = {
assetId: assetId,
title: context.resourceManager.getStringSync($r('app.string.home_page_title').id),
duration: duration,
};
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `get metadata failed, errCode = ${err.code}, errMessage = ${err.message}.`);
return;
}
try {
await this.videoSession.setAVMetadata(metadata);
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `videoSession setAVMetadata failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
}
/**
* Initializes AV session playback state from player controller.
* @param avPlayerController The player controller providing playback state.
*/
public initAvSessionPlayState(avPlayerController: VideoPlayerController) {
this.videoSession.setAVPlaybackState({
state: avPlayerController.state === 'playing' ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
position: {
elapsedTime: avPlayerController.currentTime,
updateTime: new Date().getTime(),
},
speed: 1.0,
bufferedTime: 14000,
duration: avPlayerController.durationTime
}).catch((err: BusinessError) => {
Logger.error(TAG, `setAVPlaybackState failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
}
/**
* Updates AV session playback state.
* @param state Current player state ('playing' or other).
*/
public async setAvSessionState(state: string) {
try {
await this.videoSession.setAVPlaybackState({
state: state === 'playing' ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
});
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `setAVPlaybackState setAVMetadata failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
}
/**
* Updates playback position in AV session.
* @param position Current playback position in milliseconds.
*/
public async setAvSessionPosition(position: number) {
try {
await this.videoSession.setAVPlaybackState({
position: {
elapsedTime: position,
updateTime: new Date().getTime()
}
});
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `setAVPlaybackState failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
}
/**
* Sets up AV session event listeners for playback control.
* Handles play/pause/stop commands, seeking, fast forward/rewind, and device changes.
* @param avPlayerController The player controller to manage playback.
*/
public async setAvSessionListener(avPlayerController: VideoPlayerController) {
try {
this.videoSession.on('play', () => avPlayerController.setAVPlayerPlaying());
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add play callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.on('pause', () => avPlayerController.setAVPlayerPause());
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add pause callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.on('stop', () => avPlayerController.setAVPlayerPause());
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add stop callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.on('seek', (time: number) => {
avPlayerController.avPlayerSeek(time, media.SeekMode.SEEK_CLOSEST).catch((err: BusinessError) => {
Logger.error(TAG, `avPlayerSeek failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
});
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add seek callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.on('fastForward', (time?: number) => {
if (time) {
avPlayerController.avPlayerSeek(avPlayerController.currentTime + 15000, media.SeekMode.SEEK_NEXT_SYNC)
.catch((err: BusinessError) => {
Logger.error(TAG, `avPlayerSeek failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
}
});
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add fastForward callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.on('rewind', (time?: number) => {
if (time) {
avPlayerController.avPlayerSeek(avPlayerController.currentTime - 15000, media.SeekMode.SEEK_PREV_SYNC)
.catch((err: BusinessError) => {
Logger.error(TAG, `avPlayer seek failed, errCode = ${err.code}, errMessage = ${err.message}.`);
});
}
});
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `add rewind callback failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
}
/**
* Releases AV session resources and removes all listeners.
* @param context The UIAbility context for cleanup tasks.
*/
public async releaseAvSessionListener(context: common.UIAbilityContext) {
try {
this.videoSession.off('play');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `off failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.off('pause');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `pause failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.off('stop');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `stop failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.off('seek');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `seek failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.off('fastForward');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `fastForward failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
try {
this.videoSession.off('rewind');
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `rewind failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
await BackgroundTaskManager.stopContinuousTask(context);
try {
await this.videoSession.destroy();
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `videoSession destroy failed, errCode = ${err.code}, errMessage = ${err.message}.`);
}
}
}