This document describes the typical problems that occur when audio and video apps access AVSession Kit in distributed media sessions (casting) and the methods of locating and solving the problems. You can further locate the faults based on the media session management error codes and HiLog.
Symptom
After the casting is initiated, the total duration (duration) of the video cannot be correctly obtained.
Possible causes
In the casting scenario, the total duration of a video needs to be obtained through AVCastController. If the AVCastController object is not correctly created or the media has not been played at the remote end (the duration is reported after the remote end parses the media), the total duration cannot be obtained.
Solution
Obtain the total playback duration in any of the following ways:
Method 1: Register on('playbackStateChange'). The extras field in AVPlaybackState returned by the callback carries the total media playback duration.
// Register a listener for playback state changes and obtain the total playback duration from extras.
avCastController.on('playbackStateChange', 'all', (playbackState: avSession.AVPlaybackState) => {
if (playbackState.extras?.duration !== undefined) {
let duration = playbackState.extras.duration as number;
console.info(`Cast duration: ${duration} ms`);
}
}); Method 2: Register on('mediaItemChange'). In the returned AVQueueItem, the duration field of description (AVMediaDescription) is the total media playback duration.
// Register a listener for casting media changes and obtain the total casting duration from AVQueueItem.
avCastController.on('mediaItemChange', (item: avSession.AVQueueItem) => {
let duration = item.description?.duration;
console.info(`Cast duration: ${duration} ms`);
}); Method 3: Call getCurrentItem to obtain the current casting resource information. The duration field in description in AVQueueItem is the total media playback duration.
// Obtain the current casting resource information and obtain the total projection duration from AVQueueItem.
let item: avSession.AVQueueItem = await avCastController.getCurrentItem();
let duration = item.description?.duration;
console.info(`Cast duration: ${duration} ms`); If the duration cannot be obtained using the preceding methods, ensure that the AVCastController object has been correctly created and the media file has been played on the remote end. The AVCastController object can be obtained only after the casting connection is successful. Perform operations related to duration obtaining after the casting connection is successful.
Symptom
After a video on a mobile phone is cast to a TV that supports the standard DLNA protocol, the control commands sent by the mobile phone are invalid. For example, the fast-forward, rewind, and seek commands are not responded, or the callback functions such as on('seekDone') are not triggered.
Possible causes
Solution
Symptom
After the lyrics are cast to the remote device, the remote device does not display the lyrics.
Possible causes
Solution
Symptom
When content is cast to a PC/2-in-1 device, it is played in the PC/2-in-1 player. When the mouse pointer hovers over the lyrics area, the lyrics stop scrolling. After the mouse pointer is moved away, the lyrics resume scrolling from the correct playback position.
Possible causes
This is a design behavior of the PC/2-in-1 player. When the mouse pointer hovers over the lyrics area, the PC/2-in-1 player pauses the automatic scrolling of lyrics, allowing users to view and browse the lyrics. After the mouse pointer is moved away, the lyrics automatically return to the position that matches the current playback progress and resume scrolling.
Solution
This is a normal design specification of the PC/2-in-1 player and does not need to be handled on the app side.
Symptom
After the casting, when the volume is adjusted on the local device, the actual volume change on the remote device is inconsistent with that displayed on the local device, or there is no response to the volume adjustment.
Possible causes
The devices that can be cast have hardware differences, and the maximum volume of different remote devices is inconsistent (for example, 100 for a TV device and 20 or 15 for a PC/2-in-1 device). If the volume is directly delivered based on the local volume value, it will not match the actual volume on the remote device.
Solution
You can implement volume normalization and delivery by referring to the following solution:
Listen to the on('playbackStateChange') callback of AVCastController, obtain the maximum number of volume bars (maxCastVolume) of the current peer device from the extras field of AVPlaybackState, calculate the volume change step, and convert the volume reported by the remote device to the local volume.
import { avSession } from '@kit.AVSessionKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'CastVolume';
// Reference value of the maximum volume at the local end.
const MAX_VOLUME_VALUE = 100;
// Maximum number of volume bars of the remote device (TV device: 100; PC/2-in-1 device: 20 or 15; reported by the remote device).
private maxCastVolume: number = MAX_VOLUME_VALUE;
// Step value of the remote volume change each time the system volume button is pressed. (The step value may vary depending on the remote device.)
private changeVolumeStepSize: number = 1;
// Current volume maintained by the local end.
private currentVolume: number = 0;
listenPlaybackStateChange() {
// Create an avCastController object and listen for playback state changes. Pay attention to the volume field.
avCastController.on('playbackStateChange', ['volume'], (playbackState: avSession.AVPlaybackState) => {
if (playbackState) {
this.volumeChangeHandle(playbackState);
}
});
}
volumeChangeHandle(avState: avSession.AVPlaybackState) {
// Obtain the maximum number of volume bars of the remote end from extras and convert the step.
if (avState.extras?.maxCastVolume !== undefined && typeof avState.extras?.maxCastVolume === 'number') {
this.maxCastVolume = avState.extras?.maxCastVolume as number ?? MAX_VOLUME_VALUE;
this.changeVolumeStepSize = Math.round(MAX_VOLUME_VALUE / this.maxCastVolume) || 1;
hilog.info(0x0000, TAG, `volumeChangeHandle, changeVolumeStepSize: ${this.changeVolumeStepSize}, maxCastVolume: ${this.maxCastVolume}`);
}
// Convert the volume value reported by the remote end to an integer multiple of the step value of the local end.
if (avState.volume !== undefined && avState.volume >= 0) {
let currentVolumeStep = Math.round(avState.volume * this.maxCastVolume / MAX_VOLUME_VALUE);
this.currentVolume = currentVolumeStep * this.changeVolumeStepSize;
}
} Register inputConsumer.on('keyPressed') to intercept physical volume button events. After the volume is increased or decreased based on the calculated step, the setVolume command is delivered through sendControlCommand. For details about the command type, see AVCastControlCommandType.
import { avSession } from '@kit.AVSessionKit';
import { inputConsumer, KeyCode, KeyEvent } from '@kit.InputKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'CastVolume';
const MAX_VOLUME_VALUE = 100;
// Encapsulate the casting volume setting API.
public async setAVCastVolume(volume: number): Promise<void> {
let avCommand: avSession.AVCastControlCommand = { command: 'setVolume', parameter: volume };
try {
await this.avCastController.sendControlCommand(avCommand);
} catch (error) {
let err = error as BusinessError;
hilog.error(0x0000, TAG, `setAVCastVolume failed, code: ${err.code}, message: ${err.message}`);
}
}
// Intercept the volume increase event.
public interceptIncrease() {
let upOptions: inputConsumer.KeyPressedConfig = { key: KeyCode.KEYCODE_VOLUME_UP, action: 1, isRepeat: true };
try {
inputConsumer.on('keyPressed', upOptions, async (event: KeyEvent) => {
let remoteVolume = this.currentVolume + this.changeVolumeStepSize;
this.currentVolume = remoteVolume > MAX_VOLUME_VALUE ? MAX_VOLUME_VALUE : remoteVolume;
await this.setAVCastVolume(this.currentVolume);
});
} catch (error) {
let err = error as BusinessError;
hilog.error(0x0000, TAG, `register volume up failed, code: ${err.code}, message: ${err.message}`);
}
}
// Intercept the volume decrease event.
public interceptDecrease() {
let downOptions: inputConsumer.KeyPressedConfig = { key: KeyCode.KEYCODE_VOLUME_DOWN, action: 1, isRepeat: true };
try {
inputConsumer.on('keyPressed', downOptions, async (event: KeyEvent) => {
let remoteVolume = this.currentVolume - this.changeVolumeStepSize;
this.currentVolume = remoteVolume < 0 ? 0 : remoteVolume;
await this.setAVCastVolume(this.currentVolume);
});
} catch (error) {
let err = error as BusinessError;
hilog.error(0x0000, TAG, `register volume down failed, code: ${err.code}, message: ${err.message}`);
}
} Symptom
When casting is initiated, the target remote device that can be cast cannot be found.
Possible causes
Solution