HarmonyOS 鸿蒙Next视频投播的开发总结

HarmonyOS 鸿蒙Next视频投播的开发开发准备:DevEcostudio, 真机,视频资源列表。

投播借助于媒体会话和投播组件两种方式来寻找合适的投播设备,具体实现方式如下

  1. 视频资源加入会话列表;

1.2 会话创建成功后,开启支持投播,关键代码:

this.session.setExtras({
    requireAbilityList: ['url-cast'],
})

1.3 会话创建成功后,监听投播事件: outputDeviceChange

1.4 在播放视频资源的时候,可以点击投播组件,寻找合适的投播设备:

点击投播组件,可以把正在播放的视频资源投播到合适的设备上, 关键代码: outputDeviceChange-playItem函数

import { media } from '[@kit](/user/kit).MediaKit';
import { common, wantAgent } from '[@kit](/user/kit).AbilityKit';
import { avSession } from '[@kit](/user/kit).AVSessionKit';
import { formProvider } from '[@kit](/user/kit).FormKit';
import { formBindingData } from '[@kit](/user/kit).FormKit';
import { BusinessError, emitter } from '[@kit](/user/kit).BasicServicesKit';
import { SongConstants } from '../model/SongConstants';
import { AudioPlayerState, MusicPlayMode } from '../model/MusicData';
import { SongItem } from '../model/SongData';
import { CardData } from '../model/CardData';
import { LessonsBriefIntroduction } from '../model/HearingDetailModule';
import { PreferencesUtil } from './PreferencesUtil';
import { MediaTools } from './MediaTools';
import { DataConstants } from '../const/DataConstants';

const TAG = 'MediaService';

export class MediaService {
    private context: common.UIAbilityContext = getContext() as common.UIAbilityContext;
    public avPlayer?: media.AVPlayer;
    private session?: avSession.AVSession;
    castController: avSession.AVCastController | undefined = undefined;
    private songItem: SongItem = new SongItem();
    private playMode: MusicPlayMode = MusicPlayMode.ORDER;
    private state: AudioPlayerState = AudioPlayerState.IDLE;
    private isFirst: boolean = true;
    private isPrepared: boolean = false;
    public songList: SubscribedAbstractProperty<SongItem[]> | null = null;
    private isPlay: SubscribedAbstractProperty<boolean> | null = null;
    private musicIndex: SubscribedAbstractProperty<number> | null = null;
    public audioId: SubscribedAbstractProperty<number> | null = null;
    public currentTime: SubscribedAbstractProperty<number> | null = null;
    private progress: SubscribedAbstractProperty<number> | null = null;
    private playAll: SubscribedAbstractProperty<boolean> | null = null;
    private pic: SubscribedAbstractProperty<string> | null = null;
    private isVip: SubscribedAbstractProperty<boolean> | null = null;
    private sort: SubscribedAbstractProperty<string> | null = null;
    private audioDetail: SubscribedAbstractProperty<LessonsBriefIntroduction> | null = null;
    private progressMax: SubscribedAbstractProperty<number> | null = null;
    private totalTime: SubscribedAbstractProperty<string> | null = null;
    private selectIndex: SubscribedAbstractProperty<number> | null = null;
    private mediaService: SubscribedAbstractProperty<MediaService> | null = null;
    private institutionId: SubscribedAbstractProperty<string> | null = null;
    private formIds: string[] = [];
    private isCurrent: boolean = true;
    private speed: media.PlaybackSpeed = media.PlaybackSpeed.SPEED_FORWARD_1_00_X;

    private seekCall: (seekDoneTime: number) => void = (seekDoneTime: number) => {
        this.isCurrent = true;
        console.log(TAG, `AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
        this.setPlayState({
            position: {
                elapsedTime: this.getCurrentTime(),
                updateTime: new Date().getTime()
            }
        });
    };

    private errorCall: (err: BusinessError) => void = (err: BusinessError) => {
        console.error(TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
        this.avPlayer?.reset();
    };

    private updateTimeCall: (updateTime: number) => void = (updateTime: number) => {
        let musicIndexNum = this.musicIndex?.get();
        if (this.isCurrent && musicIndexNum != null) {
            let list = this.songList?.get() as SongItem[]
            list[musicIndexNum].play_time = updateTime;
            list[musicIndexNum].isFinish = this.songItem.isFinish;
            this.songList?.set(list)
            this.currentTime?.set(updateTime)
            this.progress?.set(updateTime)
            // AppStorage.setOrCreate('currentTime', MediaTools.msToCountdownTime(updateTime));
            // AppStorage.setOrCreate<number>('progress', updateTime);
        }
    };

    private stateCall: (state: string) => Promise<void> = async (state: string) => {
        let musicIndexNum = this.musicIndex?.get()
        switch (state) {
            case 'idle':
                const time = this.currentTime?.get()
                console.info(TAG, 'AVPlayer state idle called.');
                this.state = AudioPlayerState.IDLE;
                let songListArray = this.songList?.get() as SongItem[]
                if (musicIndexNum != null) {
                    this.songItem = JSON.parse(JSON.stringify(songListArray[musicIndexNum]));
                }
                let url = this.songItem.src;
                if (this.avPlayer && url) {
                    // let avFileDescriptor: media.AVFileDescriptor = { fd: url.fd, offset: url.offset, length: url.length };
                    this.avPlayer.url = url;
                    // console.info(TAG, 'loadAsset avPlayer.url:' + this.avPlayer.fdSrc);
                }
                break;
            case 'initialized':
                console.info(TAG, 'AVPlayer state initialized called.');
                this.state = AudioPlayerState.INITIALIZED;
                if (this.avPlayer) {
                    this.avPlayer.prepare().then(() => {
                        console.info(TAG, 'AVPlayer prepare succeeded.');
                    }, (err: BusinessError) => {
                        console.error(TAG, `Invoke prepare failed, code is ${err.code}, message is ${err.message}`);
                    });
                }
                break;
            case 'prepared':
                console.info(TAG, 'AVPlayer state prepared called.');
                this.state = AudioPlayerState.PREPARED;
                this.isPrepared = true;
                this.totalTime?.set(MediaTools.msToCountdownTime(this.getDuration()))
                this.progressMax?.set(this.getDuration())
                // AppStorage.setOrCreate('totalTime', MediaTools.msToCountdownTime(this.getDuration()));
                // AppStorage.setOrCreate('progressMax', this.getDuration());
                // let isNeedVip = (this.audioDetail?.get().is_vip == '2' && !AppStorage.get<boolean>('userVip'))
                // let isNeedBuyT = (Number(this.audioDetail?.get().audio_price) != 0 && this.audioDetail?.get().is_buy == '0')
                let listenByBuy = this.audioDetail?.get().is_buy == '1';
                let listenByVip = this.audioDetail?.get().is_vip == '2' && AppStorage.get<boolean>('userVip');
                let isNeedBuy = (Number(this.audioDetail?.get().audio_price) != 0 && !(listenByBuy || listenByVip));
                let isNeedVip = (this.audioDetail?.get().is_vip == '2' && !(AppStorage.get<boolean>('userVip') || listenByBuy));
                let isAudition = this.songItem.is_audition == '1'
                if ((isNeedVip || isNeedBuy) && !isAudition && !(Number(this.institutionId?.get()) > 1)) {
                    return;
                }
                if (this.avPlayer) {
                    let time: number = this.songItem?.play_time
                    this.avPlayer.play().then(() => {
                        if (time != Number(this.songItem?.duration)) {
                            this.seek(time)
                        }
                    })
                }
                this.setAVMetadata();
                console.info(TAG, 'AVPlayer prepared succeeded.');
                break;
            case 'playing':
                console.info(TAG, 'AVPlayer state playing called.');
                if (this.avPlayer) {
                    this.avPlayer.setSpeed(this.speed);
                }
                this.state = AudioPlayerState.PLAY;
                break;
            case 'paused':
                console.info(TAG, 'AVPlayer state paused called.');
                this.state = AudioPlayerState.PAUSE;
                break;
            case 'completed':
                console.info(TAG, 'AVPlayer state completed called.');
                this.state = AudioPlayerState.COMPLETED;
                let list = this.songList?.get() as SongItem[]
                if (this.songItem.isFinish) {
                    if (musicIndexNum != null) {
                        list[musicIndexNum].isFinish = false;
                    }
                    this.songItem.isFinish = false;
                    this.avPlayer?.play()
                    return;
                } else {
                    if (musicIndexNum != null) {
                        list[musicIndexNum].isFinish = true;
                    }
                }
                this.playNextAuto(false);
                break;
            case 'stopped':
                console.info(TAG, 'AVPlayer state stopped called.');
                this.state = AudioPlayerState.STOP;
                if (this.avPlayer) {
                    this.avPlayer.reset();
                }
                break;
            case 'released':
                console.info(TAG, 'AVPlayer state released called.');
                this.state = AudioPlayerState.RELEASED;
                break;
            default:
                console.info(TAG, 'AVPlayer state unknown called.');
                this.state = AudioPlayerState.UNKNOWN;
                break;
        }
        this.updateCardData();
        this.updateIsPlay(this.state === AudioPlayerState.PLAY);
    };

    private playCall: () => void = () => {
        console.info(TAG, `on play , do play task`);
        if (this.isFirst) {
            this.loadAssent(0);
        } else {
            this.play();
        }
    };

    private pauseCall: () => void = () => {
        console.info(TAG, `on pause , do pause task`);
        this.pause();
    };

    private playNextCall: () => void = () => {
        console.info(TAG, `on playNext , do playNext task`);
        this.playNextAuto(true);
    };

    private playPreviousCall: () => void = () => {
        console.info(TAG, `on playPrevious , do playPrevious task`);
        this.playPrevious();
    };

    private putDeviceChangeCall: (
        connectState: avSession.ConnectionState,
        device: avSession.OutputDeviceInfo
    ) => void = async (
        connectState: avSession.ConnectionState,
        device: avSession.OutputDeviceInfo
    ) => {
        let currentDevice: avSession.DeviceInfo = device?.devices?.[0];
        if (currentDevice.castCategory === avSession.AVCastCategory.CATEGORY_REMOTE &&
            connectState === avSession.ConnectionState.STATE_CONNECTED) { // 设备连接成功
            console.info(`Device connected: ${device}`);
            this.castController = await this.session?.getAVCastController();
            console.info('Succeeded in getting a cast controller');
            // 查询当前播放的状态
            let avPlaybackState = await this.castController?.getAVPlaybackState();
            console.info(`Succeeded in AVPlaybackState resource obtained: ${avPlaybackState}`);
            // 监听播放状态的变化
            this.castController?.on('playbackStateChange', 'all', (state: avSession.AVPlaybackState) => {
                console.info(`Succeeded in Playback state changed: ${JSON.stringify(state)}`);
            });
            if (currentDevice.supportedProtocols === avSession.ProtocolType.TYPE_CAST_PLUS_STREAM) {
                // 此设备支持cast+投播协议
                this.playItem()
            } else if (currentDevice.supportedProtocols === avSession.ProtocolType.TYPE_DLNA) {
                // 此设备支持DLNA投播协议
                this.playItem()
            }
        }
    }

    private stopTimer: number = 0;
    private closeTime: SubscribedAbstractProperty<number> | null = null;

    constructor(audioId?: number) {
        let arr: SongItem[] = []
        this.songList = AppStorage.setAndLink('songList', arr);
        this.isPlay = AppStorage.setAndLink('isPlay', false);
        this.musicIndex = AppStorage.setAndLink('musicIndex', 0);
        this.audioId = AppStorage.setAndLink('audioId', audioId ? audioId : 0);
        this.currentTime = AppStorage.setAndLink('currentTime', 0)
        this.progress = AppStorage.setAndLink('progress', 0)
        this.playAll = AppStorage.setAndLink('playAll', true)
        this.pic = AppStorage.setAndLink('pic', '')
        this.isVip = AppStorage.setAndLink('isVip', false)
        this.sort = AppStorage.setAndLink('sort', '0')
        this.totalTime = AppStorage.setAndLink('totalTime', '')
        this.progressMax = AppStorage.setAndLink('progressMax', 0)
        this.closeTime = AppStorage.setAndLink('closeTime', 0)
        this.selectIndex = AppStorage.setAndLink('selectIndex', 0)
        this.audioDetail = AppStorage.setAndLink('audioDetail', new LessonsBriefIntroduction())
        this.institutionId = AppStorage.setAndLink('institutionId', '1')
        switch (AppStorage.get('hearPlayMode')) {
            case 'order':
                this.playMode = MusicPlayMode.ORDER;
                break;
            case 'single':
                this.playMode = MusicPlayMode.SINGLE_CYCLE;
                break;
        }
        let speedIndex = AppStorage.get<number>('hearSpeedIndex')
        this.speed = DataConstants.SPEED_LIST[speedIndex ?? 0]
    }

    public getPic(pic: string) {
        this.pic?.set(pic)
    }

    public getItem() {
        return this.songItem;
    }

    public getItemByIndex(index: number) {
        let list = this.songList?.get()
        if (list && index < list?.length) {
            return list[index];
        }
        return this.songItem;
    }

    public setSpeed(speed: media.PlaybackSpeed) {
        this.speed = speed
        if (this.avPlayer?.state != 'idle') {
            this.avPlayer?.setSpeed(speed)
        }
    }

    public static getInstance(): MediaService {
        let mediaService: MediaService | undefined = AppStorage.get('mediaService');
        if (!mediaService) {
            mediaService = new MediaService();
            AppStorage.setOrCreate('mediaService', mediaService);
        }
        return mediaService;
    }

    public getSessionList(list: SongItem[]) {
        this.songList?.set(list)
    }

    public initAudioPlayer() {
        media.createAVPlayer().then(async avPlayer => {
            if (avPlayer !== null) {
                this.avPlayer = avPlayer;
                this.setAVPlayerCallback();
                this.createSession();
            }
        }).catch((error: BusinessError) => {
            console.error(TAG, 'this avPlayer: ', `catch error happened,error code is ${error.code}`)
        })
    }

    public saveStatus() {
        let data: emitter.EventData = {
            data: {
                detail: JSON.stringify(this.songList?.get()),
                audioDetail: JSON.stringify(this.audioDetail?.get()),
                id: this.audioId?.get(),
                hearingIndex: this.musicIndex?.get(),
                sort: this.sort?.get(),
            }
        }
        emitter.emit('updateHearingDetail', data);
    }

    private addSave() {
        emitter.once('queryHearingDetailByIDBack', () => {
            let detail: string = (data.data)?.['detail']
            if (!detail) {
                let data: emitter.EventData = {
                    data: {
                        detail: JSON.stringify(this.songList?.get()),
                        audioDetail: JSON.stringify(this.audioDetail?.get()),
                        hearingId: this.audioId?.get(),
                        hearingIndex: this.musicIndex?.get(),
                        hearingCover: this.pic?.get(),
                    }
                }
                emitter.emit('addHearingDraft', data);
            }
        })
        let data: emitter.EventData = {
            data: {
                id: this.audioId?.get()
            }
        }
        emitter.emit('queryHearingDetailByID', data);
    }

    private setAVPlayerCallback() {
        console.log(`avplaerstate+${this.avPlayer?.state}`)
        if (!this.avPlayer) {
            return;
        }
        this.avPlayer.on('seekDone', this.seekCall);
        this.avPlayer.on('error', this.errorCall);
        this.avPlayer.on('timeUpdate', this.updateTimeCall);
        this.avPlayer.on('stateChange', this.stateCall)
        this.addSave()
    }

    async createSession() {
        if (!this.context) {
            return;
        }
        this.session = await avSession.createAVSession(this.context, 'CHILD_DUBBING', 'audio');
        this.session.activate();
        console.info(TAG, `session create done : sessionId : ${this.session.sessionId}`);
        this.session.setExtras({
            requireAbilityList: ['url-cast'],
        });
        this.setAVMetadata();
        let wantAgentInfo: wantAgent.WantAgentInfo = {
            wants: [
                {
                    bundleName: this.context.abilityInfo.bundleName,
                    abilityName: this.context.abilityInfo.name,
                }
            ],
            requestCode: 0,
            actionType: wantAgent.OperationType.START_ABILITIES,
            actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
        }
        wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
            if (this.session) {
                this.session.setLaunchAbility(agent);
            }
        })
        this.setListenerForMesFromController();
    }

    async setListenerForMesFromController() {
        if (!this.session) {
            return;
        }
        this.session.on('play', this.playCall);
        this.session.on('pause', this.pauseCall);
        this.session.on('playNext', this.playNextCall);
        this.session.on('playPrevious', this.playPreviousCall);
        this.session.on('outputDeviceChange', this.putDeviceChangeCall)
    }

    async unregisterSessionListener() {
        if (!this.session) {
            return;
        }
    }

    async setAVMetadata() {
        let musicIndexNum = this.musicIndex?.get()
        let id = this.musicIndex;
        let songListArray = this.songList?.get() as SongItem[]
        try {
            if (this.context && musicIndexNum != null) {
                // let mediaImage = await MediaTools.getPixelMapFromResource(this.context,
                //     this.songList[this.musicIndex].label as resourceManager.Resource);
                // console.info(TAG, 'getPixelMapFromResource success' + JSON.stringify(mediaImage));
                let metadata: avSession.AVMetadata = {
                    assetId: `${id}`,
                    title: songListArray[musicIndexNum].title,
                    artist: songListArray[musicIndexNum].singer,
                    mediaImage: songListArray[musicIndexNum].label,
                    // filter: avSession.ProtocolType.TYPE_CAST_PLUS_STREAM|avSession.ProtocolType.TYPE_DLNA,
                    duration: this.getDuration()
                };
                if (this.session) {
                    this.session.setAVMetadata(metadata).then(() => {
                        console.info(TAG, 'SetAVMetadata successfully');
                    }).catch((err: BusinessError) => {
                        console.error(TAG, `SetAVMetadata BusinessError: code: ${err.code}, message: ${err.message}`);
                    });
                }
            }
        } catch (error) {
            console.error(TAG, `SetAVMetadata try: code: ${(error as BusinessError).code},
            message: ${(error as BusinessError).message}`);
        }
    }

    /**
     * Play music by index.
     *
     * @param musicIndex
     */
    public async loadAssent(musicIndex: number) {
        let musicIndexNum = this.musicIndex?.get()
        let songListArray = this.songList?.get() as SongItem[]
        if (musicIndex >= songListArray.length) {
            console.error(TAG, `current musicIndex ${musicIndex}`);
            return;
        }
        // BackgroundUtil.startContinuousTask(this.context);
        this.updateMusicIndex(musicIndex);
        if (this.isFirst && this.avPlayer && musicIndexNum != null) {
            this.isFirst = false;
            // this.songItem = await this.songItemBuilder.build(this.songList[this.musicIndex]);
            this.songItem = songListArray[musicIndexNum];
            let url = this.songItem.src;
            if (url) {
                this.avPlayer.url = url;
                // console.info(TAG, 'loadAsset avPlayer.url:' + this.avPlayer.fdSrc);
            }
        } else {
            await this.stop();
        }
    }

    /**
     * Get whether the music is played for the first.
     *
     * @returns isFirst
     */
    public getFirst() {
        return this.isFirst;
    }

    /**
     * Set music play mode.
     *
     * @param playMode
     */
    public setPlayModel(playMode: MusicPlayMode) {
        this.playMode = playMode;
        console.info(TAG, 'setPlayModel mode: ' + this.playMode);
    }

    /**
     * Get music play mode.
     *
     * @returns playMode.
     */
    public getPlayMode(): MusicPlayMode {
        return this.playMode;
    }

    // 切换播放状态
    private updateIsPlay(isPlay: boolean) {
        this.isPlay?.set(isPlay)
        this.setPlayState({
            state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
            position: {
                elapsedTime: this.getCurrentTime(),
                updateTime: new Date().getTime()
            }
        });
    }

    /**
     * Seek play music.
     *
     * @param ms.
     */
    public seek(ms: number) {
        if (this.isPrepared && this.state != AudioPlayerState.ERROR && this.avPlayer) {
            let seekMode = this.getCurrentTime() < ms ? 0 : 1;
            let realTime = (ms <= 0 ? 0 : (ms >= this.getDuration() ? this.getDuration() : ms));
            this.isCurrent = false;
            this.avPlayer.seek(realTime, seekMode);
        }
    }

    private getCurrentTime() {
        if (this.isPrepared && this.avPlayer) {
            return this.avPlayer.currentTime;
        }
        return 0;
    }

    private getDuration() {
        if (this.isPrepared && this.avPlayer) {
            return this.avPlayer.duration;
        }
        return 0;
    }

    private start(seekMs?: number) {
        console.info(TAG, 'AVPlayer play() isPrepared:' + this.isPrepared + ', state:' + this.state + ',seek:' + seekMs);
        if (this.avPlayer) {
            let songListArray: SongItem[] | undefined = this.songList?.get()
            let musicIndexNum: number | undefined = this.musicIndex?.get()
            if (songListArray?.length && musicIndexNum) {
                this.avPlayer.url = songListArray[musicIndexNum].lesson_audio
            }
        }
    }

    /**
     * Play music.
     */
    public async play() {
        console.info(TAG, 'AVPlayer play() isPrepared:' + this.isPrepared + ', state:' + this.state);
        // BackgroundUtil.startContinuousTask(this.context);
        let listenByBuy = this.audioDetail?.get().is_buy == '1';
        let listenByVip = this.audioDetail?.get().is_vip == '2' && AppStorage.get<boolean>('userVip');
        let isNeedBuy = (Number(this.audioDetail?.get().audio_price) != 0 && !(listenByBuy || listenByVip));
        let isNeedVip = (this.audioDetail?.get().is_vip == '2' && !(AppStorage.get<boolean>('userVip') || listenByBuy));
        let isAudition = this.songItem.is_audition == '1'
        if ((isNeedVip || isNeedBuy) && !isAudition && !(Number(this.institutionId?.get()) > 1)) {
            return;
        }
        if (!this.isPrepared) {
            this.start(0);
        } else if (this.avPlayer) {
            this.avPlayer.play().then(() => {
                console.info(TAG, 'progressTime play() current time:' + this.getCurrentTime());
                this.seek(this.getCurrentTime());
                this.updateIsPlay(true);
                this.state = AudioPlayerState.PLAY;
            })
        }
    }

    /**
     * Pause music.
     */
    public pause() {
        console.info(TAG, 'AVPlayer pause() isPrepared:' + this.isPrepared + ', state:' + this.state);
        if (this.isPrepared && this.state === AudioPlayerState.PLAY && this.avPlayer) {
            this.avPlayer.pause().then(() => {
                this.state = AudioPlayerState.PAUSE;
                this.updateIsPlay(false);
            });
        }
    }

    /**
     * Play next music.
     *
     * @param isFromControl
     */
    public playNextAuto(isFromControl: boolean) {
        console.info(TAG, 'playNextAuto mode:' + this.playMode);
        switch (this.playMode) {
            case MusicPlayMode.SINGLE_CYCLE:
                if (isFromControl) {
                    this.playNext();
                } else if (this.avPlayer) {
                    this.avPlayer.play();
                }
                break;
            case MusicPlayMode.ORDER:
                this.playNext();
                break;
            case MusicPlayMode.RANDOM:
                this.playRandom();
                break;
            default:
                break;
        }
    }

    private playNext() {
        let musicIndexNum = this.musicIndex?.get()
        let songListArray = this.songList?.get() as SongItem[]
        console.info(TAG, 'playNext Index:' + this.musicIndex + ', length-1:' + (songListArray.length - 1));
        if (musicIndexNum === songListArray.length - 1) {
            this.loadAssent(0);
        } else {
            if (musicIndexNum != null) {
                this.loadAssent(musicIndexNum + 1);
            }
        }
    }

    /**
     * Play previous music.
     */
    public playPrevious() {
        let musicIndexNum = this.musicIndex?.get()
        if (musicIndexNum != null) {
            let songListArray = this.songList?.get() as SongItem[]
            switch (this.playMode) {
                case MusicPlayMode.RANDOM:
                    this.playRandom();
                    break;
                case MusicPlayMode.ORDER:
                case MusicPlayMode.SINGLE_CYCLE:
                    if (musicIndexNum === 0) {
                        this.updateMusicIndex(songListArray.length - 1);
                        musicIndexNum = songListArray.length - 1
                    } else {
                        this.updateMusicIndex(musicIndexNum--);
                    }
                    console.info(TAG, 'setLastIndex:' + this.musicIndex);
                    this.loadAssent(musicIndexNum);
                    break;
                default:
                    break;
            }
        }
    }

    private playRandom() {
        let musicIndexNum = this.musicIndex?.get()
        let songListArray = this.songList?.get() as SongItem[]
        let num = Math.round(Math.random() * (songListArray.length - 1));
        if (musicIndexNum === num) {
            this.playRandom();
        } else {
            this.updateMusicIndex(num);
            this.loadAssent(num);
        }
        console.info(TAG, 'play Random:' + this.musicIndex);
    }

    /**
     * Stop music
     */
    public async stop() {
        console.info(TAG, 'stop()');
        if (this.isPrepared && this.avPlayer) {
            await this.avPlayer.stop();
            this.updateIsPlay(false);
            this.state = AudioPlayerState.PAUSE;
        }
    }

    public setStopTime(time: number) {
        let setTime = time * 60 * 1000
        if (this.stopTimer != 0) {
            clearTimeout(this.stopTimer)
        }
        this.closeTime?.set(setTime)
        if (!time) {
            return
        }
        this.stopTimer = setInterval(() => {
            this.closeTime?.set(this.closeTime.get() - 1000)
            if (this.closeTime?.get() == 0) {
                clearInterval(this.stopTimer)
                this.pause()
            }
        }, 1000)
    }

    private async reset() {
        console.info(TAG, 'reset()');
        // await this.songItemBuilder.release();
        if (this.avPlayer) {
            await this.avPlayer.reset();
        }
        // this.isPrepared = false;
    }

    private clear() {
        this.audioDetail?.set(new LessonsBriefIntroduction());
        this.audioId?.set(0);
        this.songItem = new SongItem();
        this.songList?.set([]);
        this.isPlay?.set(false);
        this.currentTime?.set(0)
        this.musicIndex?.set(0)
        this.progress?.set(0)
        this.playAll?.set(true)
        this.pic?.set('')
        this.isVip?.set(false)
        this.sort?.set('0')
        this.totalTime?.set('')
        this.progressMax?.set(0)
        this.closeTime?.set(0)
        this.selectIndex?.set(0)
    }

    /**
     * release avPlayer.
     */
    public release() {
        if (this.avPlayer && this.session && this.context) {
            this.updateIsPlay(false);
            this.stop();
            this.reset();
            this.avPlayer.release();
            this.state = AudioPlayerState.IDLE;
            this.clear()
            // BackgroundUtil.stopContinuousTask(this.context);
            this.unregisterSessionListener();
            this.session.destroy((err: BusinessError) => {
                if (err) {
                    console.error(TAG, `Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
                } else {
                    console.info(TAG, `Destroy : SUCCESS `);
                }
            });
        }
    }

    public updateMusicIndex(musicIndex: number) {
        console.info(TAG, 'updateMusicIndex ===>' + musicIndex);
        // AppStorage.setOrCreate('selectIndex', musicIndex);
        this.selectIndex?.set(musicIndex)
        this.saveStatus()
        if (this.musicIndex?.get() !== musicIndex) {
            this.musicIndex?.set(musicIndex);
            let list = this.songList?.get();
            if (list && musicIndex) {
                this.songItem = list[musicIndex]
            }
        }
        console.info(TAG, 'this.session !== undefined ===>' + (this.session != undefined));
        if (this.session !== undefined) {
            this.setAVMetadata();
        }
    }

    public updateMusicList(musicList: SongItem[]) {
        this.songList?.set(musicList);
        this.addSave()
    }

    private async setPlayState(playbackState: avSession.AVPlaybackState) {
        if (this.session) {
            this.session.setAVPlaybackState(playbackState, (err: BusinessError) => {
                if (err) {
                    console.info(TAG, `SetAVPlaybackState BusinessError: code: ${err.code}, message: ${err.message}`);
                } else {
                    console.info(TAG, 'SetAVPlaybackState successfully');
                }
            });
        }
    }

    /**
     * Update card data.
     */
    public async updateCardData() {
        let musicIndexNum = this.musicIndex?.get()
        if (musicIndexNum != null) {
            let songListArray = this.songList?.get() as SongItem[]
            try {
                if (!this.context) {
                    return;
                }
                PreferencesUtil.getInstance().removePreferencesFromCache(this.context);
                this.formIds = await PreferencesUtil.getInstance().getFormIds(this.context);
                if (this.formIds === null || this.formIds === undefined) {
                    console.error(TAG, 'WANG formIds is null');
                    return;
                }
                let cardSongList: Array<SongItem> = [];
                if (musicIndexNum + SongConstants.ADD_INDEX_ONE === songListArray.length) {
                    cardSongList = songListArray.slice(SongConstants.SLICE_START_ZERO, SongConstants.SLICE_END_THREE);
                } else if (musicIndexNum + SongConstants.ADD_INDEX_TWO === songListArray.length) {
                    cardSongList.push(songListArray[songListArray.length - 1]);
                    cardSongList.push(songListArray[0]);
                    cardSongList.push(songListArray[1]);
                } else if (musicIndexNum + SongConstants.ADD_INDEX_THREE === songListArray.length) {
                    cardSongList = songListArray.slice(songListArray.length - SongConstants.SLICE_INDEX_TWO,
                        songListArray.length);
                    cardSongList.push(songListArray[0]);
                } else {
                    cardSongList = songListArray.slice(musicIndexNum + SongConstants.SLICE_INDEX_ONE,
                        musicIndexNum + SongConstants.SLICE_INDEX_FOUR);
                }
                let formData: CardData = {
                    isPlay: this.state === AudioPlayerState.PLAY,
                    musicName: songListArray[musicIndexNum].title,
                    musicCover: songListArray[musicIndexNum].label,
                    musicSinger: songListArray[musicIndexNum].singer,
                    cardSongList: cardSongList
                }
                let formInfo = formBindingData.createFormBindingData(formData);
                this.formIds.forEach(formId => {
                    formProvider.updateForm(formId, formInfo).then(() => {
                        console.info(TAG, 'WANG updateForm data succeed' + ', formId:' + formId);
                    }).catch((error: BusinessError) => {
                        console.error(TAG, 'updateForm err:' + JSON.stringify(error));
                        if (error.code === SongConstants.ID_NO_EXIT && this.context) {
                            PreferencesUtil.getInstance().removeFormId(this.context, formId);
                        }
                    })
                })
            } catch (error) {
                console.error(TAG, `updateCardData err: ${(error as BusinessError).code}`);
            }
        }
    }

    /**
     * Update card data on destroy.
     */
    public async updateOnDestroy() {
        if (this.formIds === null || this.formIds === undefined) {
            console.error(TAG, 'formIds is null');
            return;
        }
        let formData: Record<string, boolean> = {
            'isPlay': false
        }
        let formInfo = formBindingData.createFormBindingData(formData);
        for (let index = 0; index < this.formIds.length; index++) {
            await formProvider.updateForm(this.formIds[index], formInfo);
        }
    }

    playItem() {
        // 设置播放参数,开始播放
        let detail = this.audioDetail?.get()
        let musicNum = this.musicIndex?.get() ?? 0
        let playItem: avSession.AVQueueItem = {
            itemId: musicNum,
            description: {
                // assetId: '345174646',
                assetId: this.songItem.index.toString(),
                // title: '我推的孩子',
                title: this.songItem.title,
                // artist: '啊可大咸鱼。。。',
                artist: detail?.edit_user.nickname,
                mediaUri: this.songItem.src,
                // mediaType: 'VIDEO',
                mediaType: 'AUDIO',
                startPosition: this.currentTime?.get(),
                albumCoverUri: detail?.pic,
                albumTitle: detail?.title,
                appName: 'com.ishowedu.child.peiyinos',
            }
        };
        // 准备播放,这个不会触发真正的播放,会进行加载和缓冲
        this.castController?.prepare(playItem, () => {
            console.info('Preparation done');
        });
        // 启动播放
        this.castController?.start(playItem, () => {
            console.info('Playback started');
        });
    }

    playControl() {
        // 记录从avsession获取的远端控制器
        // 下发播放命令
        let avCommand: avSession.AVCastControlCommand = { command: 'play' };
        this.castController?.sendControlCommand(avCommand);
        // 下发暂停命令
        avCommand = { command: 'pause' };
        this.castController?.sendControlCommand(avCommand);
        // 监听上下一首切换
        this.castController?.on('playPrevious', () => {
            console.info('PlayPrevious done');
        });
        this.castController?.on('playNext', () => {
            console.info('PlayNext done');
        });
    }
}

更多关于HarmonyOS 鸿蒙Next视频投播的开发总结的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next视频投播的开发总结的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS鸿蒙Next视频投播开发总结如下:

  1. 设备发现与连接:利用分布式能力,通过DeviceManager发现并连接目标设备,确保设备在同一网络下。

  2. 投播协议:支持DLNA、Miracast等协议,使用MediaSessionMediaController管理媒体会话和控制。

  3. 媒体传输:通过MediaPlayer实现视频解码与播放,利用DistributedFile进行跨设备文件传输。

  4. UI交互:设计简洁的投播界面,使用AbilitySliceComponent实现用户操作反馈。

  5. 性能优化:减少延迟,优化网络传输和媒体解码性能,确保流畅投播体验。

  6. 兼容性测试:在不同设备上进行测试,确保投播功能稳定可靠。

回到顶部