|
@@ -1,2484 +0,0 @@
|
|
|
-/**
|
|
|
|
|
- * 独立播放器服务
|
|
|
|
|
- * 在EntryAbility中直接控制音频播放,不依赖LocalMusic
|
|
|
|
|
- * 集成UnifiedPlayerService的核心功能,确保在应用被杀死时能独立工作
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
-import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
|
|
|
-import { emitter } from '@kit.BasicServicesKit';
|
|
|
|
|
-import { common } from '@kit.AbilityKit';
|
|
|
|
|
-import { fileIo } from '@kit.CoreFileKit';
|
|
|
|
|
-import commonEventManager from '@ohos.commonEventManager';
|
|
|
|
|
-import { PLAYER_STATE_CHANGED_EVENT, PLAYER_SONG_CHANGED_EVENT } from '../widget/WidgetEventConstants';
|
|
|
|
|
-import { VideoItem } from '../../viewmodel/VideoItem';
|
|
|
|
|
-import { AppUtil, PreferencesUtil } from '@pura/harmony-utils';
|
|
|
|
|
-import {
|
|
|
|
|
- IjkMediaPlayer,
|
|
|
|
|
- InterruptEvent,
|
|
|
|
|
- InterruptHintType,
|
|
|
|
|
- OnPreparedListener,
|
|
|
|
|
- OnCompletionListener,
|
|
|
|
|
- OnErrorListener,
|
|
|
|
|
- LogUtils
|
|
|
|
|
-} from '@ohos/ijkplayer';
|
|
|
|
|
-import { WidgetData, PlayState as WidgetPlayState, SongInfo, PlayProgress, PlaylistState, WidgetConfig } from '../widget/WidgetTypes';
|
|
|
|
|
-import { UnifiedPlayerService, IPlayerService } from './UnifiedPlayerService';
|
|
|
|
|
-import { PlayerState, PlayerStateListener, PlayMode, PlayerError, PlayerErrorType } from './PlayerStateModel';
|
|
|
|
|
-import { ErrorRecoveryStrategy, IErrorRecoveryStrategy, ErrorContext, ErrorRecoveryAction } from './ErrorRecoveryStrategy';
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 播放列表信息接口
|
|
|
|
|
- */
|
|
|
|
|
-interface PlaylistInfo {
|
|
|
|
|
- songs: VideoItem[];
|
|
|
|
|
- currentIndex: number;
|
|
|
|
|
- totalCount: number;
|
|
|
|
|
- hasNext: boolean;
|
|
|
|
|
- hasPrevious: boolean;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 播放状态数据接口
|
|
|
|
|
- */
|
|
|
|
|
-interface SavedPlaybackState {
|
|
|
|
|
- isPlaying: boolean;
|
|
|
|
|
- isPaused: boolean;
|
|
|
|
|
- currentPosition: number;
|
|
|
|
|
- duration: number;
|
|
|
|
|
- timestamp: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const TAG = 'IndependentPlayerService';
|
|
|
|
|
-
|
|
|
|
|
-// 为了兼容性,定义一些本地使用的接口
|
|
|
|
|
-interface PlayState {
|
|
|
|
|
- isPlaying: boolean;
|
|
|
|
|
- isPaused: boolean;
|
|
|
|
|
- isLoading: boolean;
|
|
|
|
|
- currentPosition: number;
|
|
|
|
|
- duration: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface SongData {
|
|
|
|
|
- id: string;
|
|
|
|
|
- title: string;
|
|
|
|
|
- artist: string;
|
|
|
|
|
- album: string;
|
|
|
|
|
- filePath: string;
|
|
|
|
|
- duration: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface ProgressData {
|
|
|
|
|
- currentPosition: number;
|
|
|
|
|
- duration: number;
|
|
|
|
|
- percentage: number;
|
|
|
|
|
- currentTimeText: string;
|
|
|
|
|
- totalTimeText: string;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface PlayerStateData {
|
|
|
|
|
- playState: PlayState;
|
|
|
|
|
- currentSong: SongData;
|
|
|
|
|
- progress: ProgressData;
|
|
|
|
|
- playlist: PlaylistInfo;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface SongChangeData {
|
|
|
|
|
- currentSong: SongData;
|
|
|
|
|
- playlist: PlaylistInfo;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface PlayerCurrentState {
|
|
|
|
|
- playState: PlayState;
|
|
|
|
|
- currentSong: VideoItem | null;
|
|
|
|
|
- playlist: VideoItem[];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface DirectFormUpdateServiceInstance {
|
|
|
|
|
- updateAllForms(data: WidgetData): Promise<void>;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface DirectFormUpdateServiceClass {
|
|
|
|
|
- getInstance(): DirectFormUpdateServiceInstance;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface DirectFormUpdateServiceModule {
|
|
|
|
|
- DirectFormUpdateService: DirectFormUpdateServiceClass;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 独立播放器服务
|
|
|
|
|
- * 提供基本的音频播放控制功能,独立于LocalMusic运行
|
|
|
|
|
- * 集成UnifiedPlayerService的核心功能,确保在应用被杀死时能独立工作
|
|
|
|
|
- */
|
|
|
|
|
-export class IndependentPlayerService implements PlayerStateListener {
|
|
|
|
|
- private static instance: IndependentPlayerService;
|
|
|
|
|
- private unifiedPlayerService: IPlayerService;
|
|
|
|
|
- private errorRecovery: IErrorRecoveryStrategy;
|
|
|
|
|
- private ijkPlayer: IjkMediaPlayer | null = null;
|
|
|
|
|
- private currentSong: VideoItem | null = null;
|
|
|
|
|
- private playState: PlayState = {
|
|
|
|
|
- isPlaying: false,
|
|
|
|
|
- isPaused: false, // 初始状态设为未暂停,避免误判为可恢复播放
|
|
|
|
|
- isLoading: false,
|
|
|
|
|
- currentPosition: 0,
|
|
|
|
|
- duration: 0
|
|
|
|
|
- };
|
|
|
|
|
- private playlist: VideoItem[] = [];
|
|
|
|
|
- private currentIndex: number = 0;
|
|
|
|
|
- private isInitialized: boolean = false;
|
|
|
|
|
- private context: common.UIAbilityContext | null = null;
|
|
|
|
|
- private retryCount: number = 0;
|
|
|
|
|
- private maxRetries: number = 3;
|
|
|
|
|
- private updateProgressTimer: number = 0; // 进度更新定时器
|
|
|
|
|
- private lastProgressBroadcastTime: number = 0; // 上次进度广播时间
|
|
|
|
|
- private readonly PROGRESS_BROADCAST_INTERVAL: number = 1000; // 进度广播间隔(1秒)
|
|
|
|
|
- private lastStateBroadcastTime: number = 0; // 上次状态广播时间
|
|
|
|
|
- private readonly STATE_BROADCAST_INTERVAL: number = 500; // 状态广播间隔(0.5秒)
|
|
|
|
|
- savedDuration: number = 0; // 保存的歌曲时长
|
|
|
|
|
- private isUsingUnifiedService: boolean = false; // 标记是否使用统一服务
|
|
|
|
|
-
|
|
|
|
|
- private constructor() {
|
|
|
|
|
- this.unifiedPlayerService = UnifiedPlayerService.getInstance();
|
|
|
|
|
- this.errorRecovery = ErrorRecoveryStrategy.getInstance();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- static getInstance(): IndependentPlayerService {
|
|
|
|
|
- if (!IndependentPlayerService.instance) {
|
|
|
|
|
- IndependentPlayerService.instance = new IndependentPlayerService();
|
|
|
|
|
- }
|
|
|
|
|
- return IndependentPlayerService.instance;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 初始化服务
|
|
|
|
|
- */
|
|
|
|
|
- async initialize(context: common.UIAbilityContext): Promise<void> {
|
|
|
|
|
- if (this.isInitialized) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Service already initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- this.context = context;
|
|
|
|
|
- AppUtil.init(this.context);
|
|
|
|
|
-
|
|
|
|
|
- // 尝试初始化UnifiedPlayerService
|
|
|
|
|
- try {
|
|
|
|
|
- await this.unifiedPlayerService.initialize(context);
|
|
|
|
|
- this.unifiedPlayerService.addStateListener(this);
|
|
|
|
|
- this.isUsingUnifiedService = true;
|
|
|
|
|
- hilog.info(0x0000, TAG, '✅ Using UnifiedPlayerService for enhanced functionality');
|
|
|
|
|
-
|
|
|
|
|
- // 从UnifiedPlayerService同步数据
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- } catch (unifiedError) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Failed to initialize UnifiedPlayerService: ${unifiedError}, falling back to legacy mode`);
|
|
|
|
|
- this.isUsingUnifiedService = false;
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有的初始化逻辑
|
|
|
|
|
- this.loadFromAppStorage();
|
|
|
|
|
- await this.createAudioPlayer();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 注册卡片控制事件监听器
|
|
|
|
|
- this.registerWidgetControlListener();
|
|
|
|
|
-
|
|
|
|
|
- // 注册数据变化监听器,实时同步PreferencesUtil数据
|
|
|
|
|
- this.registerAppStorageListener();
|
|
|
|
|
-
|
|
|
|
|
- // 启动状态同步监听
|
|
|
|
|
- this.startStateSyncMonitoring();
|
|
|
|
|
-
|
|
|
|
|
- this.isInitialized = true;
|
|
|
|
|
- hilog.info(0x0000, TAG, `✅ IndependentPlayerService initialized with ${this.playlist.length} songs (unified: ${this.isUsingUnifiedService})`);
|
|
|
|
|
-
|
|
|
|
|
- // 延迟广播初始状态
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }, 1000);
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `❌ Failed to initialize IndependentPlayerService: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 创建音频播放器
|
|
|
|
|
- */
|
|
|
|
|
- private async createAudioPlayer(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Creating IjkMediaPlayer instance');
|
|
|
|
|
-
|
|
|
|
|
- this.ijkPlayer = IjkMediaPlayer.getInstance();
|
|
|
|
|
-
|
|
|
|
|
- // 设置音频模式 - 关键修复
|
|
|
|
|
- this.ijkPlayer.setAudioId('independentPlayer');
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkPlayer audioId set to: independentPlayer');
|
|
|
|
|
-
|
|
|
|
|
- // 设置音频中断处理
|
|
|
|
|
- this.setupAudioInterruptHandler();
|
|
|
|
|
-
|
|
|
|
|
- // 设置播放器回调
|
|
|
|
|
- this.setupPlayerCallbacks();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkMediaPlayer created and configured successfully');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to create IjkMediaPlayer: ${error}`);
|
|
|
|
|
- throw new Error(`Failed to create IjkMediaPlayer: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 配置IjkPlayer选项(完全参考LocalMusic的配置)
|
|
|
|
|
- */
|
|
|
|
|
- private setupIjkPlayerOptions(): void {
|
|
|
|
|
- if (!this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- // 设置调试模式
|
|
|
|
|
- this.ijkPlayer.setDebug(false);
|
|
|
|
|
-
|
|
|
|
|
- // 初始化配置
|
|
|
|
|
- this.ijkPlayer.native_setup();
|
|
|
|
|
-
|
|
|
|
|
- // 设置音量(纯音频播放)
|
|
|
|
|
- this.ijkPlayer.setVolume('1.0', '1.0');
|
|
|
|
|
-
|
|
|
|
|
- // 使用精确寻帧
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", "1");
|
|
|
|
|
-
|
|
|
|
|
- // 动态缓冲区大小配置(参考LocalMusic)
|
|
|
|
|
- let bufferSize = 1 * 1024 * 1024; // 默认1MB缓冲区
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", bufferSize.toString());
|
|
|
|
|
-
|
|
|
|
|
- // 重连模式
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", "1");
|
|
|
|
|
-
|
|
|
|
|
- // 缓冲帧数设置
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100");
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-frames", "1000");
|
|
|
|
|
-
|
|
|
|
|
- // 启动预加载
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");
|
|
|
|
|
-
|
|
|
|
|
- // 设置无缓冲
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");
|
|
|
|
|
-
|
|
|
|
|
- // 跳帧处理
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5");
|
|
|
|
|
-
|
|
|
|
|
- // 最大缓冲cache
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");
|
|
|
|
|
-
|
|
|
|
|
- // 无限制收流
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");
|
|
|
|
|
- this.ijkPlayer.setOptionLong(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");
|
|
|
|
|
-
|
|
|
|
|
- // 屏幕常亮(音频播放也保持)
|
|
|
|
|
- this.ijkPlayer.setScreenOnWhilePlaying(true);
|
|
|
|
|
-
|
|
|
|
|
- // 设置超时
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "10000000");
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "10000000");
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000");
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000");
|
|
|
|
|
-
|
|
|
|
|
- // 变速播放支持
|
|
|
|
|
- this.ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "1");
|
|
|
|
|
- this.ijkPlayer.setSpeed('1.0f');
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkPlayer options configured with LocalMusic settings');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置音频中断处理
|
|
|
|
|
- */
|
|
|
|
|
- private setupAudioInterruptHandler(): void {
|
|
|
|
|
- if (!this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- const audioInterruptCallback = (event: InterruptEvent) => {
|
|
|
|
|
- hilog.info(0x0000, TAG, `Audio interrupt event: ${JSON.stringify(event)}`);
|
|
|
|
|
-
|
|
|
|
|
- if (event.hintType === InterruptHintType.INTERRUPT_HINT_PAUSE) {
|
|
|
|
|
- this.pausePlayback();
|
|
|
|
|
- } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_RESUME) {
|
|
|
|
|
- // 可选择是否自动恢复播放
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Audio interrupt resumed, manual resume required');
|
|
|
|
|
- } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_STOP) {
|
|
|
|
|
- this.stopPlaybackSync();
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.ijkPlayer.on('audioInterrupt', audioInterruptCallback);
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Audio interrupt handler set');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置播放器回调
|
|
|
|
|
- */
|
|
|
|
|
- private setupPlayerCallbacks(): void {
|
|
|
|
|
- if (!this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- // 准备完成回调
|
|
|
|
|
- const onPreparedListener: OnPreparedListener = {
|
|
|
|
|
- onPrepared: () => {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkPlayer prepared successfully');
|
|
|
|
|
-
|
|
|
|
|
- // 现在可以安全地开始播放
|
|
|
|
|
- try {
|
|
|
|
|
- this.ijkPlayer?.start();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback started after onPrepared');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to start playback in onPrepared: ${error}`);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.duration = this.ijkPlayer?.getDuration() || 0;
|
|
|
|
|
- this.playState.isPlaying = true;
|
|
|
|
|
- this.playState.isPaused = false;
|
|
|
|
|
-
|
|
|
|
|
- // 保存播放状态
|
|
|
|
|
- this.savePlaybackState();
|
|
|
|
|
-
|
|
|
|
|
- // 立即广播状态更新
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- // 启动进度更新定时器
|
|
|
|
|
- this.startProgressTask();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playback prepared and started for ${this.currentSong?.name}, duration: ${this.playState.duration}ms`);
|
|
|
|
|
-
|
|
|
|
|
- // 延迟恢复播放位置
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- this.restorePlaybackPosition();
|
|
|
|
|
- }, 500);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- this.ijkPlayer.setOnPreparedListener(onPreparedListener);
|
|
|
|
|
-
|
|
|
|
|
- // 播放完成回调
|
|
|
|
|
- const onCompletionListener: OnCompletionListener = {
|
|
|
|
|
- onCompletion: () => {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback completed');
|
|
|
|
|
- this.handlePlaybackCompletion();
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- this.ijkPlayer.setOnCompletionListener(onCompletionListener);
|
|
|
|
|
-
|
|
|
|
|
- // 错误回调
|
|
|
|
|
- const onErrorListener: OnErrorListener = {
|
|
|
|
|
- onError: (what: number, extra: number) => {
|
|
|
|
|
- hilog.error(0x0000, TAG, `IjkPlayer error: what=${what}, extra=${extra}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- // 使用错误恢复策略处理错误
|
|
|
|
|
- this.handlePlayerError(what, extra);
|
|
|
|
|
-
|
|
|
|
|
- return true; // 表示错误已处理
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- this.ijkPlayer.setOnErrorListener(onErrorListener);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Player callbacks configured');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理播放器错误
|
|
|
|
|
- */
|
|
|
|
|
- private async handlePlayerError(what: number, extra: number): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 根据错误代码确定错误类型
|
|
|
|
|
- let errorType: PlayerErrorType = PlayerErrorType.PLAYBACK_ERROR;
|
|
|
|
|
- let errorMessage = `Player error: what=${what}, extra=${extra}`;
|
|
|
|
|
-
|
|
|
|
|
- // 根据ijkPlayer的错误代码映射错误类型
|
|
|
|
|
- if (what === -1004 || what === -1007) {
|
|
|
|
|
- errorType = PlayerErrorType.NETWORK_ERROR;
|
|
|
|
|
- errorMessage = 'Network connection error';
|
|
|
|
|
- } else if (what === -1010 || what === -1011) {
|
|
|
|
|
- errorType = PlayerErrorType.FILE_NOT_FOUND;
|
|
|
|
|
- errorMessage = 'File not found or cannot be accessed';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 创建播放器错误对象
|
|
|
|
|
- const playerError = new PlayerError(errorType, errorMessage, what);
|
|
|
|
|
- playerError.retryCount = this.retryCount;
|
|
|
|
|
-
|
|
|
|
|
- // 创建错误上下文
|
|
|
|
|
- const context: ErrorContext = {
|
|
|
|
|
- currentSong: this.currentSong,
|
|
|
|
|
- playlist: this.playlist,
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- retryCount: this.retryCount,
|
|
|
|
|
- isNetworkAvailable: true // 这里可以实际检查网络状态
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 获取恢复策略
|
|
|
|
|
- const recoveryAction = await this.errorRecovery.handleError(playerError, context);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Error recovery action: ${recoveryAction}`);
|
|
|
|
|
-
|
|
|
|
|
- // 执行恢复动作
|
|
|
|
|
- await this.executeErrorRecoveryAction(recoveryAction, playerError);
|
|
|
|
|
-
|
|
|
|
|
- } catch (recoveryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Error recovery failed: ${recoveryError}`);
|
|
|
|
|
- this.retryCount = 0;
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 执行错误恢复动作
|
|
|
|
|
- */
|
|
|
|
|
- private async executeErrorRecoveryAction(action: ErrorRecoveryAction, error: PlayerError): Promise<void> {
|
|
|
|
|
- switch (action) {
|
|
|
|
|
- case ErrorRecoveryAction.RETRY:
|
|
|
|
|
- await this.retryPlayback(error);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.SKIP_TO_NEXT:
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.STOP_PLAYBACK:
|
|
|
|
|
- await this.stopPlayback();
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.WAIT_AND_RETRY:
|
|
|
|
|
- await this.waitAndRetryPlayback(error);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.SHOW_ERROR:
|
|
|
|
|
- hilog.error(0x0000, TAG, `Playback error: ${error.message}`);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Unknown recovery action: ${action}`);
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 重试播放
|
|
|
|
|
- */
|
|
|
|
|
- private async retryPlayback(error: PlayerError): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.retryCount < this.maxRetries) {
|
|
|
|
|
- this.retryCount++;
|
|
|
|
|
- const delay = this.errorRecovery.getRetryDelay(this.retryCount);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Retrying playback in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})`);
|
|
|
|
|
-
|
|
|
|
|
- setTimeout(async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.play();
|
|
|
|
|
- } catch (retryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Retry failed: ${retryError}`);
|
|
|
|
|
- await this.handlePlayerError(-1, -1); // 递归处理重试失败
|
|
|
|
|
- }
|
|
|
|
|
- }, delay);
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'Max retries exceeded, skipping to next song');
|
|
|
|
|
- this.retryCount = 0;
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- } catch (retryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Retry setup failed: ${retryError}`);
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 等待后重试播放
|
|
|
|
|
- */
|
|
|
|
|
- private async waitAndRetryPlayback(error: PlayerError): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.retryCount < this.maxRetries) {
|
|
|
|
|
- this.retryCount++;
|
|
|
|
|
- const delay = this.errorRecovery.getRetryDelay(this.retryCount);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Waiting ${delay}ms before retry (network error)`);
|
|
|
|
|
-
|
|
|
|
|
- // 更新状态为加载中
|
|
|
|
|
- this.playState.isLoading = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- setTimeout(async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.play();
|
|
|
|
|
- } catch (retryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Network retry failed: ${retryError}`);
|
|
|
|
|
- await this.handlePlayerError(-1004, -1); // 网络错误代码
|
|
|
|
|
- }
|
|
|
|
|
- }, delay);
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'Max network retries exceeded, skipping to next song');
|
|
|
|
|
- this.retryCount = 0;
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- } catch (waitError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Wait and retry setup failed: ${waitError}`);
|
|
|
|
|
- await this.skipToNextSong();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 跳到下一首歌曲(错误恢复)
|
|
|
|
|
- */
|
|
|
|
|
- private async skipToNextSong(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Skipping to next song due to error');
|
|
|
|
|
-
|
|
|
|
|
- // 重置重试计数
|
|
|
|
|
- this.retryCount = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.unifiedPlayerService.playNext();
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- } catch (unifiedError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `UnifiedPlayerService playNext failed: ${unifiedError}`);
|
|
|
|
|
- // 回退到本地处理
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否有下一首
|
|
|
|
|
- if (this.currentIndex < this.playlist.length - 1) {
|
|
|
|
|
- this.currentIndex++;
|
|
|
|
|
- this.currentSong = this.playlist[this.currentIndex];
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- // 尝试播放下一首
|
|
|
|
|
- await this.play();
|
|
|
|
|
- } else {
|
|
|
|
|
- // 没有下一首,停止播放
|
|
|
|
|
- hilog.info(0x0000, TAG, 'No next song available, stopping playback');
|
|
|
|
|
- await this.stopPlayback();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- } catch (skipError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to skip to next song: ${skipError}`);
|
|
|
|
|
- await this.stopPlayback();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 停止播放(错误恢复)
|
|
|
|
|
- */
|
|
|
|
|
- private async stopPlayback(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- this.ijkPlayer.stop();
|
|
|
|
|
- this.ijkPlayer.reset();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.currentPosition = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 停止进度更新定时器
|
|
|
|
|
- if (this.updateProgressTimer > 0) {
|
|
|
|
|
- clearInterval(this.updateProgressTimer);
|
|
|
|
|
- this.updateProgressTimer = 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 广播状态更新
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback stopped due to error');
|
|
|
|
|
- } catch (stopError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to stop playback: ${stopError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从PreferencesUtil加载播放列表数据,保持与LocalMusic一致
|
|
|
|
|
- */
|
|
|
|
|
- private loadFromAppStorage(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 直接从PreferencesUtil获取数据,与LocalMusic保持一致
|
|
|
|
|
- this.playlist = PreferencesUtil.getSync('LastMusicList', []) as Array<VideoItem>;
|
|
|
|
|
- this.currentSong = PreferencesUtil.getSync('LastMusicInfo', undefined) as VideoItem;
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Loaded ${this.playlist.length} songs from PreferencesUtil`);
|
|
|
|
|
-
|
|
|
|
|
- // 如果有播放列表但没有当前歌曲,使用第一首歌
|
|
|
|
|
- if (this.playlist.length > 0 && !this.currentSong) {
|
|
|
|
|
- this.currentSong = this.playlist[0];
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Using first song as current song');
|
|
|
|
|
- } else if (this.currentSong && this.playlist.length > 0) {
|
|
|
|
|
- // 在播放列表中查找当前歌曲的索引
|
|
|
|
|
- const foundIndex = this.playlist.findIndex(song =>
|
|
|
|
|
- song.id === this.currentSong!.id ||
|
|
|
|
|
- song.filePath === this.currentSong!.filePath ||
|
|
|
|
|
- song.name === this.currentSong!.name
|
|
|
|
|
- );
|
|
|
|
|
- this.currentIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
|
|
|
- hilog.info(0x0000, TAG, `Found current song index: ${this.currentIndex}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 没有播放列表或当前歌曲
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = null;
|
|
|
|
|
- hilog.info(0x0000, TAG, 'No playlist or current song found');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 恢复播放状态(包括duration等信息)
|
|
|
|
|
- if (this.currentSong) {
|
|
|
|
|
- this.restorePlaybackState();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到AppStorage以便其他组件访问
|
|
|
|
|
- AppStorage.setOrCreate('songList', this.playlist);
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', this.currentIndex);
|
|
|
|
|
- AppStorage.setOrCreate('currentSong', this.currentSong);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Current index: ${this.currentIndex}, current song: ${this.currentSong?.name || 'none'}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to load from PreferencesUtil: ${error}`);
|
|
|
|
|
- this.playlist = [];
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = null;
|
|
|
|
|
-
|
|
|
|
|
- // 设置默认值到AppStorage
|
|
|
|
|
- AppStorage.setOrCreate('songList', []);
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', 0);
|
|
|
|
|
- AppStorage.setOrCreate('currentSong', null);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 注册数据变化监听器(主要监听PreferencesUtil数据变化)
|
|
|
|
|
- */
|
|
|
|
|
- private registerAppStorageListener(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 设置初始值到AppStorage
|
|
|
|
|
- AppStorage.setOrCreate('songList', this.playlist);
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', this.currentIndex);
|
|
|
|
|
-
|
|
|
|
|
- // 定期检查PreferencesUtil中的数据变化
|
|
|
|
|
- setInterval(() => {
|
|
|
|
|
- this.checkPreferencesChanges();
|
|
|
|
|
- }, 1000);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'PreferencesUtil data listeners registered successfully');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to register data listeners: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 检查PreferencesUtil中的数据变化
|
|
|
|
|
- */
|
|
|
|
|
- private checkPreferencesChanges(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 检查播放列表变化
|
|
|
|
|
- const newPlaylist = PreferencesUtil.getSync('LastMusicList', []) as Array<VideoItem>;
|
|
|
|
|
- const newCurrentSong = PreferencesUtil.getSync('LastMusicInfo', undefined) as VideoItem;
|
|
|
|
|
-
|
|
|
|
|
- if (JSON.stringify(newPlaylist) !== JSON.stringify(this.playlist)) {
|
|
|
|
|
- const oldLength = this.playlist.length;
|
|
|
|
|
- this.playlist = newPlaylist;
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playlist updated from PreferencesUtil: ${oldLength} -> ${this.playlist.length} songs`);
|
|
|
|
|
-
|
|
|
|
|
- // 如果当前索引超出范围,重置为0
|
|
|
|
|
- if (this.currentIndex >= this.playlist.length) {
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = this.playlist.length > 0 ? this.playlist[0] : null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到AppStorage
|
|
|
|
|
- AppStorage.setOrCreate('songList', this.playlist);
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查当前歌曲变化
|
|
|
|
|
- if (newCurrentSong && JSON.stringify(newCurrentSong) !== JSON.stringify(this.currentSong)) {
|
|
|
|
|
- this.currentSong = newCurrentSong;
|
|
|
|
|
-
|
|
|
|
|
- // 在播放列表中查找新歌曲的索引
|
|
|
|
|
- if (this.playlist.length > 0) {
|
|
|
|
|
- const foundIndex = this.playlist.findIndex(song =>
|
|
|
|
|
- song.id === newCurrentSong.id ||
|
|
|
|
|
- song.filePath === newCurrentSong.filePath ||
|
|
|
|
|
- song.name === newCurrentSong.name
|
|
|
|
|
- );
|
|
|
|
|
- if (foundIndex >= 0) {
|
|
|
|
|
- this.currentIndex = foundIndex;
|
|
|
|
|
- hilog.info(0x0000, TAG, `Current song updated from PreferencesUtil: ${this.currentIndex}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到AppStorage
|
|
|
|
|
- AppStorage.setOrCreate('currentSong', this.currentSong);
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', this.currentIndex);
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to check PreferencesUtil changes: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 刷新播放列表数据(从PreferencesUtil重新加载)
|
|
|
|
|
- */
|
|
|
|
|
- private refreshPlaylistData(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- const oldPlaylistLength = this.playlist.length;
|
|
|
|
|
- const oldCurrentIndex = this.currentIndex;
|
|
|
|
|
-
|
|
|
|
|
- // 如果当前播放列表为空,强制重新加载
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playlist is empty, forcing reload from storage...');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.loadFromAppStorage();
|
|
|
|
|
-
|
|
|
|
|
- // 如果数据有变化,广播更新
|
|
|
|
|
- if (this.playlist.length !== oldPlaylistLength || this.currentIndex !== oldCurrentIndex) {
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playlist data refreshed: ${oldPlaylistLength} -> ${this.playlist.length} songs, index: ${oldCurrentIndex} -> ${this.currentIndex}`);
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- } else if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'Still no songs found after refresh');
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to refresh playlist data: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 注册卡片控制事件监听器
|
|
|
|
|
- */
|
|
|
|
|
- private registerWidgetControlListener(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- const innerEvent: emitter.InnerEvent = { eventId: 9001 };
|
|
|
|
|
- emitter.on(innerEvent, (eventData: emitter.EventData) => {
|
|
|
|
|
- this.handleWidgetControlEvent(eventData);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Widget control listener registered');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to register widget control listener: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理卡片控制事件
|
|
|
|
|
- */
|
|
|
|
|
- private handleWidgetControlEvent(eventData: emitter.EventData): void {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = eventData.data as Record<string, Object>;
|
|
|
|
|
- const command = data['command'] as string;
|
|
|
|
|
- const source = data['source'] as string;
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `🎵 IndependentPlayer received command: ${command} from ${source}`);
|
|
|
|
|
-
|
|
|
|
|
- switch (command) {
|
|
|
|
|
- case 'PLAY_PAUSE':
|
|
|
|
|
- this.togglePlayPause();
|
|
|
|
|
- break;
|
|
|
|
|
- case 'NEXT_SONG':
|
|
|
|
|
- this.playNext();
|
|
|
|
|
- break;
|
|
|
|
|
- case 'PREV_SONG':
|
|
|
|
|
- this.playPrevious();
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Unknown command: ${command}`);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle widget control event: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从UnifiedPlayerService同步数据
|
|
|
|
|
- */
|
|
|
|
|
- private syncFromUnifiedService(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.isUsingUnifiedService) return;
|
|
|
|
|
-
|
|
|
|
|
- this.playlist = this.unifiedPlayerService.getPlaylist();
|
|
|
|
|
- this.currentIndex = this.unifiedPlayerService.getCurrentIndex();
|
|
|
|
|
- this.currentSong = this.unifiedPlayerService.getCurrentSong();
|
|
|
|
|
-
|
|
|
|
|
- const unifiedState = this.unifiedPlayerService.getCurrentState();
|
|
|
|
|
- this.playState = {
|
|
|
|
|
- isPlaying: unifiedState.isPlaying,
|
|
|
|
|
- isPaused: unifiedState.isPaused,
|
|
|
|
|
- isLoading: unifiedState.isLoading,
|
|
|
|
|
- currentPosition: unifiedState.currentPosition,
|
|
|
|
|
- duration: unifiedState.duration
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Synced from UnifiedPlayerService: ${this.playlist.length} songs, current: ${this.currentSong?.name || 'none'}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to sync from UnifiedPlayerService: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 同步状态到主应用
|
|
|
|
|
- * 确保IndependentPlayerService的状态变化能够反映到主应用
|
|
|
|
|
- */
|
|
|
|
|
- private syncStateToMainApp(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 同步到AppStorage,供主应用组件访问
|
|
|
|
|
- AppStorage.setOrCreate('songList', this.playlist);
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', this.currentIndex);
|
|
|
|
|
- AppStorage.setOrCreate('currentSong', this.currentSong);
|
|
|
|
|
- AppStorage.setOrCreate('isPlaying', this.playState.isPlaying);
|
|
|
|
|
- AppStorage.setOrCreate('isPaused', this.playState.isPaused);
|
|
|
|
|
- AppStorage.setOrCreate('currentPosition', this.playState.currentPosition);
|
|
|
|
|
- AppStorage.setOrCreate('duration', this.playState.duration);
|
|
|
|
|
-
|
|
|
|
|
- // 同步到PreferencesUtil,确保数据持久化
|
|
|
|
|
- if (this.currentSong) {
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicInfo', this.currentSong);
|
|
|
|
|
- }
|
|
|
|
|
- if (this.playlist.length > 0) {
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', this.playlist);
|
|
|
|
|
- }
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', this.currentIndex);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `State synced to main app: ${this.currentSong?.name || 'none'}, index: ${this.currentIndex}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to sync state to main app: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 切换播放/暂停
|
|
|
|
|
- */
|
|
|
|
|
- async togglePlayPause(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- const currentState = this.unifiedPlayerService.getCurrentState();
|
|
|
|
|
- if (currentState.isPlaying) {
|
|
|
|
|
- await this.unifiedPlayerService.pause();
|
|
|
|
|
- } else {
|
|
|
|
|
- await this.unifiedPlayerService.startPlayOrResumePlay();
|
|
|
|
|
- }
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- // 尝试重新初始化
|
|
|
|
|
- if (this.context) {
|
|
|
|
|
- await this.initialize(this.context);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果没有播放列表,尝试刷新数据
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'No playlist found, attempting to refresh data...');
|
|
|
|
|
- this.refreshPlaylistData();
|
|
|
|
|
-
|
|
|
|
|
- // 如果刷新后仍然没有数据,返回
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'No songs available after refresh');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 使用内部状态来判断,而不是完全依赖ijkPlayer.isPlaying()
|
|
|
|
|
- const shouldPause = this.playState.isPlaying;
|
|
|
|
|
- hilog.info(0x0000, TAG, `Toggle action: shouldPause=${shouldPause}, current state - isPlaying=${this.playState.isPlaying}, isPaused=${this.playState.isPaused}, ijkPlayer.isPlaying=${this.ijkPlayer.isPlaying()}`);
|
|
|
|
|
-
|
|
|
|
|
- if (shouldPause) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Pausing playback');
|
|
|
|
|
- await this.pause();
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Starting/Resuming playback');
|
|
|
|
|
- await this.play();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to toggle play/pause: ${error}`);
|
|
|
|
|
- // 如果出错,尝试重置播放器
|
|
|
|
|
- try {
|
|
|
|
|
- await this.resetPlayer();
|
|
|
|
|
- } catch (resetError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to reset player after toggle error: ${resetError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 恢复播放状态(包括duration等信息)
|
|
|
|
|
- */
|
|
|
|
|
- private restorePlaybackState(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.currentSong) return;
|
|
|
|
|
-
|
|
|
|
|
- // 恢复duration信息
|
|
|
|
|
- const savedDuration = AppStorage.get<number>(`duration_${this.currentSong.filePath}`) || 0;
|
|
|
|
|
- if (savedDuration > 0) {
|
|
|
|
|
- this.playState.duration = savedDuration;
|
|
|
|
|
- this.savedDuration = savedDuration; // 确保savedDuration也被设置
|
|
|
|
|
- hilog.info(0x0000, TAG, `Restored duration: ${savedDuration}ms for ${this.currentSong.name}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 恢复播放状态
|
|
|
|
|
- const stateStr = AppStorage.get<string>(`state_${this.currentSong.filePath}`) || '';
|
|
|
|
|
- if (stateStr) {
|
|
|
|
|
- try {
|
|
|
|
|
- const stateData: SavedPlaybackState = JSON.parse(stateStr) as SavedPlaybackState;
|
|
|
|
|
- // 检查状态是否太旧(超过24小时不恢复)
|
|
|
|
|
- if (stateData && Date.now() - stateData.timestamp < 24 * 60 * 60 * 1000) {
|
|
|
|
|
- // 恢复暂停状态,但不自动开始播放
|
|
|
|
|
- this.playState.isPaused = stateData.isPaused;
|
|
|
|
|
- this.playState.isPlaying = false; // 启动时总是设为未播放
|
|
|
|
|
- this.playState.currentPosition = stateData.currentPosition || 0;
|
|
|
|
|
-
|
|
|
|
|
- // 如果从单独的duration存储中没有获取到duration,尝试从状态数据中获取
|
|
|
|
|
- if (savedDuration === 0 && stateData.duration > 0) {
|
|
|
|
|
- this.playState.duration = stateData.duration;
|
|
|
|
|
- this.savedDuration = stateData.duration;
|
|
|
|
|
- hilog.info(0x0000, TAG, `Used duration from state data: ${stateData.duration}ms`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Restored playback state: isPaused=${this.playState.isPaused}, position=${this.playState.currentPosition}, duration=${this.savedDuration}`);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (parseError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to parse saved state: ${parseError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果没有保存的状态,设置默认值
|
|
|
|
|
- if (!stateStr) {
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true; // 默认为暂停状态
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.currentPosition = 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Final restored state for ${this.currentSong.name}: isPlaying=${this.playState.isPlaying}, isPaused=${this.playState.isPaused}, duration=${this.savedDuration}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to restore playback state: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存播放状态
|
|
|
|
|
- */
|
|
|
|
|
- private savePlaybackState(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.currentSong) return;
|
|
|
|
|
-
|
|
|
|
|
- // 保存duration信息
|
|
|
|
|
- if (this.playState.duration > 0) {
|
|
|
|
|
- AppStorage.setOrCreate(`duration_${this.currentSong.filePath}`, this.playState.duration);
|
|
|
|
|
- hilog.info(0x0000, TAG, `Saved duration: ${this.playState.duration}ms for ${this.currentSong.name}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 保存播放状态
|
|
|
|
|
- const stateData: SavedPlaybackState = {
|
|
|
|
|
- isPlaying: this.playState.isPlaying,
|
|
|
|
|
- isPaused: this.playState.isPaused,
|
|
|
|
|
- currentPosition: this.playState.currentPosition || 0,
|
|
|
|
|
- duration: this.playState.duration || 0,
|
|
|
|
|
- timestamp: Date.now()
|
|
|
|
|
- };
|
|
|
|
|
- AppStorage.setOrCreate(`state_${this.currentSong.filePath}`, JSON.stringify(stateData));
|
|
|
|
|
- hilog.info(0x0000, TAG, `Saved playback state: isPlaying=${this.playState.isPlaying}, isPaused=${this.playState.isPaused}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to save playback state: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 播放
|
|
|
|
|
- */
|
|
|
|
|
- async play(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.startPlayOrResumePlay();
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 刷新播放列表数据
|
|
|
|
|
- this.refreshPlaylistData();
|
|
|
|
|
-
|
|
|
|
|
- // 如果没有当前歌曲,尝试播放第一首
|
|
|
|
|
- if (!this.currentSong && this.playlist.length > 0) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'No current song, playing first song');
|
|
|
|
|
- await this.playSongAtIndex(0);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!this.currentSong) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'No song to play');
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果已经在播放,无需操作
|
|
|
|
|
- if (this.ijkPlayer.isPlaying()) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Already playing');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否可以恢复播放(优先使用保存的duration)
|
|
|
|
|
- const ijkDuration = this.ijkPlayer.getDuration();
|
|
|
|
|
- const savedDuration = this.playState.duration;
|
|
|
|
|
- const effectiveDuration = savedDuration > 0 ? savedDuration : ijkDuration;
|
|
|
|
|
-
|
|
|
|
|
- const canResume = this.playState.isPaused &&
|
|
|
|
|
- !this.playState.isLoading &&
|
|
|
|
|
- effectiveDuration > 0 && // 使用有效的duration
|
|
|
|
|
- !this.ijkPlayer.isPlaying(); // 确保确实没在播放
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Play state check: isPaused=${this.playState.isPaused}, isLoading=${this.playState.isLoading}, ijkDuration=${ijkDuration}, savedDuration=${savedDuration}, effectiveDuration=${effectiveDuration}, canResume=${canResume}`);
|
|
|
|
|
-
|
|
|
|
|
- if (canResume) {
|
|
|
|
|
- try {
|
|
|
|
|
- this.ijkPlayer.start();
|
|
|
|
|
- this.playState.isPlaying = true;
|
|
|
|
|
- this.playState.isPaused = false;
|
|
|
|
|
- // 恢复duration如果播放器中为0
|
|
|
|
|
- if (ijkDuration === 0 && savedDuration > 0) {
|
|
|
|
|
- this.playState.duration = savedDuration;
|
|
|
|
|
- }
|
|
|
|
|
- this.startProgressTask(); // 启动进度更新
|
|
|
|
|
- this.syncStateToMainApp(); // 同步状态到主应用
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Resumed playback from pause');
|
|
|
|
|
- return;
|
|
|
|
|
- } catch (resumeError) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Failed to resume from pause: ${resumeError}, starting fresh`);
|
|
|
|
|
- // 恢复失败,重置状态继续执行重新播放逻辑
|
|
|
|
|
- this.playState.isPaused = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 开始播放当前歌曲(重新准备数据源)
|
|
|
|
|
- hilog.info(0x0000, TAG, `Attempting to play song: ${this.currentSong.filePath}`);
|
|
|
|
|
- await this.playWithSimpleLogic();
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to play: ${error}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 播放指定索引的歌曲
|
|
|
|
|
- */
|
|
|
|
|
- private async playSongAtIndex(index: number): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- if (index < 0 || index >= this.playlist.length) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Invalid song index: ${index}`);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const song = this.playlist[index];
|
|
|
|
|
- this.currentIndex = index;
|
|
|
|
|
- this.currentSong = song;
|
|
|
|
|
-
|
|
|
|
|
- // 更新AppStorage中的currIndex,与LocalMusic保持同步
|
|
|
|
|
- AppStorage.setOrCreate('currIndex', this.currentIndex);
|
|
|
|
|
-
|
|
|
|
|
- this.playState.isLoading = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Switching to song: ${song.name}`);
|
|
|
|
|
-
|
|
|
|
|
- // 使用简单播放逻辑
|
|
|
|
|
- try {
|
|
|
|
|
- await this.playWithSimpleLogic();
|
|
|
|
|
-
|
|
|
|
|
- // 恢复播放进度(记忆播放功能)
|
|
|
|
|
- this.restorePlaybackPosition();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Successfully switched to song: ${song.name} by ${song.artist || '未知艺术家'}`);
|
|
|
|
|
-
|
|
|
|
|
- } catch (playError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to switch song: ${playError}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- throw new Error(`Failed to switch song: ${playError}`);
|
|
|
|
|
- }
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- // 广播歌曲变化
|
|
|
|
|
- this.broadcastSongChange();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to play song at index ${index}: ${error}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 恢复播放进度(记忆播放功能)
|
|
|
|
|
- */
|
|
|
|
|
- private restorePlaybackPosition(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.currentSong || !this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- // 从PreferencesUtil获取保存的播放位置
|
|
|
|
|
- const savedPosition = AppStorage.get<number>(`playback_${this.currentSong.filePath}`) || 0;
|
|
|
|
|
-
|
|
|
|
|
- if (savedPosition > 0) {
|
|
|
|
|
- // 延迟恢复播放位置,确保播放器已准备好
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- if (this.ijkPlayer && this.ijkPlayer.isPlaying()) {
|
|
|
|
|
- this.ijkPlayer.seekTo(savedPosition.toString());
|
|
|
|
|
- hilog.info(0x0000, TAG, `Restored playback position: ${savedPosition}ms for ${this.currentSong?.name}`);
|
|
|
|
|
- }
|
|
|
|
|
- }, 500);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to restore playback position: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存播放进度
|
|
|
|
|
- */
|
|
|
|
|
- private savePlaybackPosition(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.currentSong || !this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- const position = this.playState.currentPosition;
|
|
|
|
|
- const duration = this.playState.duration;
|
|
|
|
|
- const threshold = 5000; // 5秒阈值
|
|
|
|
|
-
|
|
|
|
|
- // 如果播放位置接近末尾,则保存为0
|
|
|
|
|
- const playbackPosition = (duration - position < threshold) ? 0 : position;
|
|
|
|
|
-
|
|
|
|
|
- // 保存到AppStorage,这样LocalMusic也能访问到
|
|
|
|
|
- AppStorage.setOrCreate(`playback_${this.currentSong.filePath}`, playbackPosition);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Saved playback position: ${playbackPosition}ms for ${this.currentSong.name}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to save playback position: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 暂停
|
|
|
|
|
- */
|
|
|
|
|
- async pause(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.pause();
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 保存播放进度
|
|
|
|
|
- this.savePlaybackPosition();
|
|
|
|
|
-
|
|
|
|
|
- this.pausePlayback();
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback paused');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to pause: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 停止播放
|
|
|
|
|
- */
|
|
|
|
|
- async stop(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.stop();
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- this.stopPlaybackSync();
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback stopped');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to stop: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 跳转到指定位置
|
|
|
|
|
- */
|
|
|
|
|
- async seekTo(position: string): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.seekTo(position);
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const positionMs = parseInt(position);
|
|
|
|
|
- if (isNaN(positionMs) || positionMs < 0) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Invalid seek position: ${position}`);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.ijkPlayer.seekTo(position);
|
|
|
|
|
- this.playState.currentPosition = positionMs;
|
|
|
|
|
-
|
|
|
|
|
- // 保存播放进度
|
|
|
|
|
- this.savePlaybackPosition();
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Seeked to position: ${position}ms`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to seek: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置播放速度
|
|
|
|
|
- */
|
|
|
|
|
- setPlaybackSpeed(speed: number): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- this.unifiedPlayerService.setPlaybackSpeed(speed);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.ijkPlayer.setSpeed(`${speed}f`);
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playback speed set to: ${speed}x`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to set playback speed: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置音量
|
|
|
|
|
- */
|
|
|
|
|
- setVolume(volume: number): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- this.unifiedPlayerService.setVolume(volume);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer not initialized');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const volumeStr = Math.max(0, Math.min(1, volume)).toString();
|
|
|
|
|
- this.ijkPlayer.setVolume(volumeStr, volumeStr);
|
|
|
|
|
- hilog.info(0x0000, TAG, `Volume set to: ${volume}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to set volume: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理播放完成
|
|
|
|
|
- */
|
|
|
|
|
- private handlePlaybackCompletion(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.currentPosition = 0;
|
|
|
|
|
- this.stopProgressTask(); // 停止进度更新
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- // 自动播放下一首
|
|
|
|
|
- this.playNext().catch(() => {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to auto play next`);
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle playback completion: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 简单播放逻辑(修复回调顺序问题)
|
|
|
|
|
- */
|
|
|
|
|
- private async playWithSimpleLogic(): Promise<void> {
|
|
|
|
|
- return new Promise<void>((resolve, reject) => {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.ijkPlayer || !this.currentSong) {
|
|
|
|
|
- reject(new Error('IjkPlayer or currentSong not available'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Setting data source: ${this.currentSong.filePath}`);
|
|
|
|
|
-
|
|
|
|
|
- // 重置播放器状态
|
|
|
|
|
- this.ijkPlayer.reset();
|
|
|
|
|
- this.playState.isLoading = true;
|
|
|
|
|
-
|
|
|
|
|
- // 正确的IjkPlayer初始化顺序
|
|
|
|
|
- // 1. 设置调试模式
|
|
|
|
|
- this.ijkPlayer.setDebug(false);
|
|
|
|
|
-
|
|
|
|
|
- // 2. 初始化配置
|
|
|
|
|
- this.ijkPlayer.native_setup();
|
|
|
|
|
-
|
|
|
|
|
- // 3. 设置音量
|
|
|
|
|
- this.ijkPlayer.setVolume('1.0', '1.0');
|
|
|
|
|
-
|
|
|
|
|
- // 4. 设置数据源
|
|
|
|
|
- this.ijkPlayer.setDataSource(this.currentSong.filePath);
|
|
|
|
|
-
|
|
|
|
|
- // 5. 设置数据源头部(可选,对于本地文件通常不需要)
|
|
|
|
|
- let headers = new Map([
|
|
|
|
|
- ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],
|
|
|
|
|
- ["referer", "https://www.bilibili.com"]
|
|
|
|
|
- ]);
|
|
|
|
|
- this.ijkPlayer.setDataSourceHeader(headers);
|
|
|
|
|
-
|
|
|
|
|
- // 6. 配置所有播放器选项
|
|
|
|
|
- this.setupIjkPlayerOptions();
|
|
|
|
|
-
|
|
|
|
|
- // 7. 设置回调监听器(重要!必须在setMessageListener之前)
|
|
|
|
|
- this.setupPlayerCallbacks();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Player callbacks configured');
|
|
|
|
|
-
|
|
|
|
|
- // 8. 激活消息监听(必须在回调设置完成后,prepareAsync之前调用)
|
|
|
|
|
- this.ijkPlayer.setMessageListener();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Message listener activated');
|
|
|
|
|
-
|
|
|
|
|
- // 9. 异步准备(不要立即调用start,等待onPrepared回调)
|
|
|
|
|
- this.ijkPlayer.prepareAsync();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'PrepareAsync called, waiting for onPrepared callback');
|
|
|
|
|
-
|
|
|
|
|
- // 设置加载状态并广播
|
|
|
|
|
- this.playState.isLoading = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkPlayer configuration and start completed');
|
|
|
|
|
-
|
|
|
|
|
- // 立即resolve,不等待onPrepared(onPrepared会在回调中处理播放状态)
|
|
|
|
|
- resolve();
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed in playWithSimpleLogic: ${error}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- reject(error);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 播放下一首
|
|
|
|
|
- */
|
|
|
|
|
- async playNext(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.playNext();
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- // 刷新播放列表数据
|
|
|
|
|
- this.refreshPlaylistData();
|
|
|
|
|
-
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'No songs in playlist');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const nextIndex = (this.currentIndex + 1) % this.playlist.length;
|
|
|
|
|
- await this.playSongAtIndex(nextIndex);
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to play next: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 播放上一首
|
|
|
|
|
- */
|
|
|
|
|
- async playPrevious(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 如果使用统一服务,委托给统一服务处理
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- await this.unifiedPlayerService.playPrevious();
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 回退到原有逻辑
|
|
|
|
|
- // 刷新播放列表数据
|
|
|
|
|
- this.refreshPlaylistData();
|
|
|
|
|
-
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'No songs in playlist');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const prevIndex = this.currentIndex === 0 ? this.playlist.length - 1 : this.currentIndex - 1;
|
|
|
|
|
- await this.playSongAtIndex(prevIndex);
|
|
|
|
|
-
|
|
|
|
|
- // 同步状态到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to play previous: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理播放器状态变化
|
|
|
|
|
- */
|
|
|
|
|
- private handlePlayerStateChange(state: string): void {
|
|
|
|
|
- switch (state) {
|
|
|
|
|
- case 'playing':
|
|
|
|
|
- this.playState.isPlaying = true;
|
|
|
|
|
- this.playState.isPaused = false;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- break;
|
|
|
|
|
- case 'paused':
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- break;
|
|
|
|
|
- case 'stopped':
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.currentPosition = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case 'prepared':
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- break;
|
|
|
|
|
- case 'error':
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 广播播放器状态
|
|
|
|
|
- */
|
|
|
|
|
- private broadcastPlayerState(): void {
|
|
|
|
|
- // 节流:避免短时间内重复广播
|
|
|
|
|
- const now = Date.now();
|
|
|
|
|
- if (now - this.lastStateBroadcastTime < this.STATE_BROADCAST_INTERVAL) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- this.lastStateBroadcastTime = now;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const playlistInfo: PlaylistInfo = {
|
|
|
|
|
- songs: this.playlist,
|
|
|
|
|
- hasNext: this.currentIndex < this.playlist.length - 1,
|
|
|
|
|
- hasPrevious: this.currentIndex > 0,
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- totalCount: this.playlist.length
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const currentSongData: SongData = this.currentSong ? {
|
|
|
|
|
- id: this.currentSong.id,
|
|
|
|
|
- title: this.currentSong.name,
|
|
|
|
|
- artist: this.currentSong.artist || '未知艺术家',
|
|
|
|
|
- album: this.currentSong.album || '未知专辑',
|
|
|
|
|
- filePath: this.currentSong.filePath,
|
|
|
|
|
- duration: parseInt(this.currentSong.duration || '0')
|
|
|
|
|
- } : {
|
|
|
|
|
- id: '',
|
|
|
|
|
- title: '暂无播放',
|
|
|
|
|
- artist: '未知艺术家',
|
|
|
|
|
- album: '未知专辑',
|
|
|
|
|
- filePath: '',
|
|
|
|
|
- duration: 0
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 获取实时播放位置
|
|
|
|
|
- const currentPosition = this.ijkPlayer?.getCurrentPosition() || this.playState.currentPosition;
|
|
|
|
|
-
|
|
|
|
|
- const progressData: ProgressData = {
|
|
|
|
|
- currentPosition: currentPosition,
|
|
|
|
|
- duration: this.playState.duration,
|
|
|
|
|
- percentage: this.playState.duration > 0 ? (currentPosition / this.playState.duration) * 100 : 0,
|
|
|
|
|
- currentTimeText: this.formatTime(currentPosition / 1000),
|
|
|
|
|
- totalTimeText: this.formatTime(this.playState.duration / 1000)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 创建完整的WidgetData对象(与LocalMusic保持一致)
|
|
|
|
|
- const widgetPlayState: WidgetPlayState = {
|
|
|
|
|
- isPlaying: this.playState.isPlaying,
|
|
|
|
|
- isPaused: this.playState.isPaused,
|
|
|
|
|
- isLoading: this.playState.isLoading
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const widgetSongInfo: SongInfo = this.currentSong ? {
|
|
|
|
|
- id: this.currentSong.id,
|
|
|
|
|
- title: this.currentSong.name,
|
|
|
|
|
- artist: this.currentSong.artist || '未知艺术家',
|
|
|
|
|
- album: this.currentSong.album || '未知专辑',
|
|
|
|
|
- coverImagePath: this.currentSong.pixelMapPath || '',
|
|
|
|
|
- duration: parseInt(this.currentSong.duration || '0')
|
|
|
|
|
- } : {
|
|
|
|
|
- id: '',
|
|
|
|
|
- title: '暂无播放',
|
|
|
|
|
- artist: '未知艺术家',
|
|
|
|
|
- album: '未知专辑',
|
|
|
|
|
- coverImagePath: '',
|
|
|
|
|
- duration: 0
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Broadcasting song info: artist="${this.currentSong?.artist}", title="${this.currentSong?.name}"`);
|
|
|
|
|
-
|
|
|
|
|
- const widgetProgress: PlayProgress = {
|
|
|
|
|
- currentPosition: currentPosition,
|
|
|
|
|
- duration: this.playState.duration,
|
|
|
|
|
- percentage: this.playState.duration > 0 ? (currentPosition / this.playState.duration) * 100 : 0,
|
|
|
|
|
- currentTimeText: this.formatTime(currentPosition / 1000),
|
|
|
|
|
- totalTimeText: this.formatTime(this.playState.duration / 1000)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const widgetPlaylist: PlaylistState = {
|
|
|
|
|
- hasNext: this.currentIndex < this.playlist.length - 1,
|
|
|
|
|
- hasPrevious: this.currentIndex > 0,
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- totalCount: this.playlist.length
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const widgetConfig: WidgetConfig = {
|
|
|
|
|
- size: 'medium',
|
|
|
|
|
- theme: 'auto',
|
|
|
|
|
- showProgress: true,
|
|
|
|
|
- showCover: true
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const widgetData: WidgetData = {
|
|
|
|
|
- playState: widgetPlayState,
|
|
|
|
|
- currentSong: widgetSongInfo,
|
|
|
|
|
- progress: widgetProgress,
|
|
|
|
|
- playlist: widgetPlaylist,
|
|
|
|
|
- config: widgetConfig
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 直接通过 DirectFormUpdateService 更新卡片数据(与LocalMusic保持一致)
|
|
|
|
|
- try {
|
|
|
|
|
- import('../widget/DirectFormUpdateService').then((module: DirectFormUpdateServiceModule) => {
|
|
|
|
|
- const directFormService = module.DirectFormUpdateService.getInstance();
|
|
|
|
|
- directFormService.updateAllForms(widgetData).then(() => {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Direct form update completed successfully');
|
|
|
|
|
- }).catch((error: Error) => {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Direct form update failed: ${error.message}`);
|
|
|
|
|
- });
|
|
|
|
|
- }).catch((importError: Error) => {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to import DirectFormUpdateService: ${importError.message}`);
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to update widget via DirectFormUpdateService: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const stateData: PlayerStateData = {
|
|
|
|
|
- playState: this.playState,
|
|
|
|
|
- currentSong: currentSongData,
|
|
|
|
|
- progress: progressData,
|
|
|
|
|
- playlist: playlistInfo
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const publishInfo: commonEventManager.CommonEventPublishData = {
|
|
|
|
|
- data: JSON.stringify(stateData)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- commonEventManager.publish(PLAYER_STATE_CHANGED_EVENT, publishInfo, (err) => {
|
|
|
|
|
- if (err) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast player state: ${err}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.info(0x0000, TAG, `Player state broadcasted: playing=${this.playState.isPlaying}, song=${this.currentSong?.name || 'none'}`);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast player state: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 广播歌曲变化
|
|
|
|
|
- */
|
|
|
|
|
- private broadcastSongChange(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.currentSong) return;
|
|
|
|
|
-
|
|
|
|
|
- const playlistInfo: PlaylistInfo = {
|
|
|
|
|
- songs: this.playlist,
|
|
|
|
|
- hasNext: this.currentIndex < this.playlist.length - 1,
|
|
|
|
|
- hasPrevious: this.currentIndex > 0,
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- totalCount: this.playlist.length
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const currentSongData: SongData = {
|
|
|
|
|
- id: this.currentSong.id,
|
|
|
|
|
- title: this.currentSong.name,
|
|
|
|
|
- artist: this.currentSong.artist || '未知艺术家',
|
|
|
|
|
- album: this.currentSong.album || '未知专辑',
|
|
|
|
|
- filePath: this.currentSong.filePath,
|
|
|
|
|
- duration: parseInt(this.currentSong.duration || '0')
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const songData: SongChangeData = {
|
|
|
|
|
- currentSong: currentSongData,
|
|
|
|
|
- playlist: playlistInfo
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const publishInfo: commonEventManager.CommonEventPublishData = {
|
|
|
|
|
- data: JSON.stringify(songData)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- commonEventManager.publish(PLAYER_SONG_CHANGED_EVENT, publishInfo, (err) => {
|
|
|
|
|
- if (err) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast song change: ${err}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.info(0x0000, TAG, `Song change broadcasted: ${this.currentSong?.name}`);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast song change: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 启动进度更新定时器
|
|
|
|
|
- */
|
|
|
|
|
- private startProgressTask(): void {
|
|
|
|
|
- // 避免重复启动定时器
|
|
|
|
|
- if (this.updateProgressTimer > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.updateProgressTimer = setInterval(() => {
|
|
|
|
|
- if (this.ijkPlayer && this.playState.isPlaying) {
|
|
|
|
|
- this.updateProgress();
|
|
|
|
|
- }
|
|
|
|
|
- }, 300); // 与LocalMusic保持一致:300ms间隔
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Progress update timer started');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 停止进度更新定时器
|
|
|
|
|
- */
|
|
|
|
|
- private stopProgressTask(): void {
|
|
|
|
|
- if (this.updateProgressTimer > 0) {
|
|
|
|
|
- clearInterval(this.updateProgressTimer);
|
|
|
|
|
- this.updateProgressTimer = 0;
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Progress update timer stopped');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 更新播放进度
|
|
|
|
|
- */
|
|
|
|
|
- private updateProgress(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.ijkPlayer || !this.currentSong) return;
|
|
|
|
|
-
|
|
|
|
|
- const currentPosition = this.ijkPlayer.getCurrentPosition() || 0;
|
|
|
|
|
- const duration = this.playState.duration;
|
|
|
|
|
-
|
|
|
|
|
- // 更新内部状态
|
|
|
|
|
- this.playState.currentPosition = currentPosition;
|
|
|
|
|
-
|
|
|
|
|
- // 节流广播进度更新到卡片
|
|
|
|
|
- this.broadcastProgressIfNeeded();
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to update progress: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 节流广播进度更新
|
|
|
|
|
- */
|
|
|
|
|
- private broadcastProgressIfNeeded(): void {
|
|
|
|
|
- const now = Date.now();
|
|
|
|
|
- if (now - this.lastProgressBroadcastTime >= this.PROGRESS_BROADCAST_INTERVAL) {
|
|
|
|
|
- this.lastProgressBroadcastTime = now;
|
|
|
|
|
- this.broadcastPlayerProgress();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 广播播放进度到卡片
|
|
|
|
|
- */
|
|
|
|
|
- private broadcastPlayerProgress(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- const currentPos = this.ijkPlayer?.getCurrentPosition() || 0;
|
|
|
|
|
- const progressData: ProgressData = {
|
|
|
|
|
- currentPosition: currentPos,
|
|
|
|
|
- duration: this.playState.duration || 0,
|
|
|
|
|
- percentage: this.playState.duration > 0 ? (currentPos / this.playState.duration) * 100 : 0,
|
|
|
|
|
- currentTimeText: this.formatTime(currentPos / 1000),
|
|
|
|
|
- totalTimeText: this.formatTime(this.playState.duration / 1000)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 广播播放进度变化
|
|
|
|
|
- const publishInfo: commonEventManager.CommonEventPublishData = {
|
|
|
|
|
- data: JSON.stringify(progressData)
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- commonEventManager.publish('com.ttmusic.player.progress', publishInfo, (err) => {
|
|
|
|
|
- if (err) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast progress: ${err}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- hilog.info(0x0000, TAG, `Progress broadcasted: ${progressData.percentage.toFixed(1)}%`);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to broadcast progress: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 格式化时间显示
|
|
|
|
|
- */
|
|
|
|
|
- private formatTime(seconds: number): string {
|
|
|
|
|
- const mins = Math.floor(seconds / 60);
|
|
|
|
|
- const secs = Math.floor(seconds % 60);
|
|
|
|
|
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 暂停播放
|
|
|
|
|
- */
|
|
|
|
|
- private pausePlayback(): void {
|
|
|
|
|
- if (!this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.ijkPlayer.isPlaying()) {
|
|
|
|
|
- this.ijkPlayer.pause();
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.stopProgressTask(); // 停止进度更新
|
|
|
|
|
- this.savePlaybackState(); // 保存播放状态
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback paused');
|
|
|
|
|
- } else {
|
|
|
|
|
- // 即使没在播放,也要确保状态正确
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.stopProgressTask();
|
|
|
|
|
- this.savePlaybackState();
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback paused (was not playing)');
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to pause playback: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 开始播放
|
|
|
|
|
- */
|
|
|
|
|
- private startPlayback(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Starting playback after prepared');
|
|
|
|
|
- // 在LocalMusic模式下,播放器已经在prepareAsync后自动开始
|
|
|
|
|
- // 这里只需要更新状态
|
|
|
|
|
- this.playState.isPlaying = true;
|
|
|
|
|
- this.playState.isPaused = false;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playback confirmed started for ${this.currentSong?.name}`);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to start playback: ${error}`);
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 停止播放(同步版本)
|
|
|
|
|
- */
|
|
|
|
|
- private stopPlaybackSync(): void {
|
|
|
|
|
- if (!this.ijkPlayer) return;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.ijkPlayer.isPlaying()) {
|
|
|
|
|
- this.ijkPlayer.stop();
|
|
|
|
|
- }
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.currentPosition = 0;
|
|
|
|
|
- this.stopProgressTask(); // 停止进度更新
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playback stopped');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to stop playback: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 等待播放器达到指定状态
|
|
|
|
|
- */
|
|
|
|
|
- private async waitForPlayerReady(timeout: number = 3000): Promise<void> {
|
|
|
|
|
- return new Promise<void>((resolve, reject) => {
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- reject(new Error('IjkPlayer is null'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 对于 IjkPlayer,我们简单地检查它是否已创建并可用
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- resolve();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const timeoutId = setTimeout(() => {
|
|
|
|
|
- reject(new Error(`Timeout waiting for player ready`));
|
|
|
|
|
- }, timeout);
|
|
|
|
|
-
|
|
|
|
|
- // 简单延迟后解析,因为 IjkPlayer 不需要复杂的状态等待
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- clearTimeout(timeoutId);
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- resolve();
|
|
|
|
|
- } else {
|
|
|
|
|
- reject(new Error('Player not ready'));
|
|
|
|
|
- }
|
|
|
|
|
- }, 100);
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 重置播放器(用于错误恢复)
|
|
|
|
|
- */
|
|
|
|
|
- private async resetPlayer(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.ijkPlayer) {
|
|
|
|
|
- hilog.error(0x0000, TAG, 'IjkPlayer is null, cannot reset');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Resetting IjkPlayer`);
|
|
|
|
|
-
|
|
|
|
|
- // 先尝试停止播放器
|
|
|
|
|
- try {
|
|
|
|
|
- if (this.ijkPlayer.isPlaying()) {
|
|
|
|
|
- this.ijkPlayer.pause();
|
|
|
|
|
- }
|
|
|
|
|
- this.ijkPlayer.stop();
|
|
|
|
|
- } catch (stopError) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Failed to stop player during reset: ${stopError}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 重新创建播放器
|
|
|
|
|
- await this.recreatePlayer();
|
|
|
|
|
- hilog.info(0x0000, TAG, `Player reset completed`);
|
|
|
|
|
-
|
|
|
|
|
- // 重置播放状态
|
|
|
|
|
- this.playState = {
|
|
|
|
|
- isPlaying: false,
|
|
|
|
|
- isPaused: true,
|
|
|
|
|
- isLoading: false,
|
|
|
|
|
- currentPosition: 0,
|
|
|
|
|
- duration: 0
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to reset player: ${error}`);
|
|
|
|
|
- // 如果重置失败,尝试重新创建播放器
|
|
|
|
|
- await this.recreatePlayer();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 重新创建播放器(最后的恢复手段)
|
|
|
|
|
- */
|
|
|
|
|
- private async recreatePlayer(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- hilog.warn(0x0000, TAG, 'Recreating IjkMediaPlayer...');
|
|
|
|
|
-
|
|
|
|
|
- // 释放旧播放器
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- try {
|
|
|
|
|
- this.ijkPlayer.release();
|
|
|
|
|
- } catch (releaseError) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Failed to release old player: ${releaseError}`);
|
|
|
|
|
- }
|
|
|
|
|
- this.ijkPlayer = null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 重置重试计数
|
|
|
|
|
- this.retryCount = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 创建新播放器
|
|
|
|
|
- await this.createAudioPlayer();
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IjkMediaPlayer recreated successfully');
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to recreate IjkMediaPlayer: ${error}`);
|
|
|
|
|
- this.isInitialized = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取当前播放状态
|
|
|
|
|
- */
|
|
|
|
|
- getCurrentState(): PlayerCurrentState {
|
|
|
|
|
- const currentState: PlayerCurrentState = {
|
|
|
|
|
- playState: {
|
|
|
|
|
- isPlaying: this.playState.isPlaying,
|
|
|
|
|
- isPaused: this.playState.isPaused,
|
|
|
|
|
- isLoading: this.playState.isLoading,
|
|
|
|
|
- currentPosition: this.playState.currentPosition,
|
|
|
|
|
- duration: this.playState.duration
|
|
|
|
|
- },
|
|
|
|
|
- currentSong: this.currentSong,
|
|
|
|
|
- playlist: this.playlist.slice()
|
|
|
|
|
- };
|
|
|
|
|
- return currentState;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 独立播放列表管理功能
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置播放列表
|
|
|
|
|
- */
|
|
|
|
|
- setPlaylist(songs: VideoItem[], currentIndex: number = 0): void {
|
|
|
|
|
- try {
|
|
|
|
|
- this.playlist = songs.slice(); // 创建副本
|
|
|
|
|
- this.currentIndex = Math.max(0, Math.min(currentIndex, songs.length - 1));
|
|
|
|
|
- this.currentSong = songs.length > 0 ? songs[this.currentIndex] : null;
|
|
|
|
|
-
|
|
|
|
|
- // 同步到存储
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', this.playlist);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', this.currentIndex);
|
|
|
|
|
- if (this.currentSong) {
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicInfo', this.currentSong);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Playlist set: ${songs.length} songs, current index: ${this.currentIndex}`);
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to set playlist: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 添加歌曲到播放列表
|
|
|
|
|
- */
|
|
|
|
|
- addToPlaylist(song: VideoItem, index?: number): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (index === undefined || index < 0 || index > this.playlist.length) {
|
|
|
|
|
- // 添加到末尾
|
|
|
|
|
- this.playlist.push(song);
|
|
|
|
|
- hilog.info(0x0000, TAG, `Added song to end of playlist: ${song.name}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 插入到指定位置
|
|
|
|
|
- this.playlist.splice(index, 0, song);
|
|
|
|
|
- // 如果插入位置在当前播放位置之前,需要调整当前索引
|
|
|
|
|
- if (index <= this.currentIndex) {
|
|
|
|
|
- this.currentIndex++;
|
|
|
|
|
- }
|
|
|
|
|
- hilog.info(0x0000, TAG, `Inserted song at index ${index}: ${song.name}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 如果这是第一首歌,设置为当前歌曲
|
|
|
|
|
- if (this.playlist.length === 1) {
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = song;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到存储和主应用
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', this.playlist);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', this.currentIndex);
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to add song to playlist: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从播放列表移除歌曲
|
|
|
|
|
- */
|
|
|
|
|
- removeFromPlaylist(index: number): VideoItem | null {
|
|
|
|
|
- try {
|
|
|
|
|
- if (index < 0 || index >= this.playlist.length) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Invalid index for removal: ${index}`);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const removedSong = this.playlist.splice(index, 1)[0];
|
|
|
|
|
-
|
|
|
|
|
- // 调整当前播放索引
|
|
|
|
|
- if (index < this.currentIndex) {
|
|
|
|
|
- // 移除的歌曲在当前播放歌曲之前
|
|
|
|
|
- this.currentIndex--;
|
|
|
|
|
- } else if (index === this.currentIndex) {
|
|
|
|
|
- // 移除的是当前播放的歌曲
|
|
|
|
|
- if (this.playlist.length === 0) {
|
|
|
|
|
- // 播放列表为空
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = null;
|
|
|
|
|
- this.stopPlaybackSync();
|
|
|
|
|
- } else {
|
|
|
|
|
- // 调整到有效索引
|
|
|
|
|
- if (this.currentIndex >= this.playlist.length) {
|
|
|
|
|
- this.currentIndex = this.playlist.length - 1;
|
|
|
|
|
- }
|
|
|
|
|
- this.currentSong = this.playlist[this.currentIndex];
|
|
|
|
|
-
|
|
|
|
|
- // 如果正在播放,切换到新歌曲
|
|
|
|
|
- if (this.playState.isPlaying) {
|
|
|
|
|
- this.playSongAtIndex(this.currentIndex).catch((error: Error) => {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to play song after removal: ${error.message}`);
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // 如果移除的歌曲在当前播放歌曲之后,不需要调整索引
|
|
|
|
|
-
|
|
|
|
|
- // 同步到存储和主应用
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', this.playlist);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', this.currentIndex);
|
|
|
|
|
- if (this.currentSong) {
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicInfo', this.currentSong);
|
|
|
|
|
- }
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Removed song from playlist: ${removedSong.name}, new playlist size: ${this.playlist.length}`);
|
|
|
|
|
- return removedSong;
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to remove song from playlist: ${error}`);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 移动播放列表中的歌曲
|
|
|
|
|
- */
|
|
|
|
|
- moveInPlaylist(fromIndex: number, toIndex: number): boolean {
|
|
|
|
|
- try {
|
|
|
|
|
- if (fromIndex < 0 || fromIndex >= this.playlist.length ||
|
|
|
|
|
- toIndex < 0 || toIndex >= this.playlist.length ||
|
|
|
|
|
- fromIndex === toIndex) {
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Invalid indices for move: from=${fromIndex}, to=${toIndex}`);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const song = this.playlist.splice(fromIndex, 1)[0];
|
|
|
|
|
- this.playlist.splice(toIndex, 0, song);
|
|
|
|
|
-
|
|
|
|
|
- // 调整当前播放索引
|
|
|
|
|
- if (fromIndex === this.currentIndex) {
|
|
|
|
|
- // 移动的是当前播放的歌曲
|
|
|
|
|
- this.currentIndex = toIndex;
|
|
|
|
|
- } else if (fromIndex < this.currentIndex && toIndex >= this.currentIndex) {
|
|
|
|
|
- // 从当前播放位置之前移动到之后
|
|
|
|
|
- this.currentIndex--;
|
|
|
|
|
- } else if (fromIndex > this.currentIndex && toIndex <= this.currentIndex) {
|
|
|
|
|
- // 从当前播放位置之后移动到之前
|
|
|
|
|
- this.currentIndex++;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 同步到存储和主应用
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', this.playlist);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', this.currentIndex);
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Moved song from index ${fromIndex} to ${toIndex}: ${song.name}`);
|
|
|
|
|
- return true;
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to move song in playlist: ${error}`);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 清空播放列表
|
|
|
|
|
- */
|
|
|
|
|
- clearPlaylist(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- this.stopPlaybackSync();
|
|
|
|
|
- this.playlist = [];
|
|
|
|
|
- this.currentIndex = 0;
|
|
|
|
|
- this.currentSong = null;
|
|
|
|
|
-
|
|
|
|
|
- // 同步到存储和主应用
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicList', []);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicIndex', 0);
|
|
|
|
|
- PreferencesUtil.putSync('LastMusicInfo', null);
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Playlist cleared');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to clear playlist: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取播放列表信息
|
|
|
|
|
- */
|
|
|
|
|
- getPlaylistInfo(): PlaylistInfo {
|
|
|
|
|
- return {
|
|
|
|
|
- songs: this.playlist.slice(),
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- totalCount: this.playlist.length,
|
|
|
|
|
- hasNext: this.currentIndex < this.playlist.length - 1,
|
|
|
|
|
- hasPrevious: this.currentIndex > 0
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * PlayerStateListener 接口实现
|
|
|
|
|
- * 处理来自UnifiedPlayerService的状态变化
|
|
|
|
|
- */
|
|
|
|
|
- onStateChanged(state: PlayerState): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 更新本地状态
|
|
|
|
|
- this.playState.isPlaying = state.isPlaying;
|
|
|
|
|
- this.playState.isPaused = state.isPaused;
|
|
|
|
|
- this.playState.isLoading = state.isLoading;
|
|
|
|
|
- this.playState.currentPosition = state.currentPosition;
|
|
|
|
|
- this.playState.duration = state.duration;
|
|
|
|
|
-
|
|
|
|
|
- // 更新播放列表相关状态
|
|
|
|
|
- if (state.currentIndex !== undefined) {
|
|
|
|
|
- this.currentIndex = state.currentIndex;
|
|
|
|
|
- }
|
|
|
|
|
- if (state.currentSong) {
|
|
|
|
|
- this.currentSong = state.currentSong;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `State changed from UnifiedPlayerService: playing=${state.isPlaying}, song=${state.currentSong?.name || 'none'}`);
|
|
|
|
|
-
|
|
|
|
|
- // 同步到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- // 广播状态变化
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle state change: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onSongChanged(song: VideoItem): void {
|
|
|
|
|
- try {
|
|
|
|
|
- this.currentSong = song;
|
|
|
|
|
-
|
|
|
|
|
- // 在播放列表中查找新歌曲的索引
|
|
|
|
|
- if (this.playlist.length > 0) {
|
|
|
|
|
- const foundIndex = this.playlist.findIndex(s =>
|
|
|
|
|
- s.id === song.id ||
|
|
|
|
|
- s.filePath === song.filePath ||
|
|
|
|
|
- s.name === song.name
|
|
|
|
|
- );
|
|
|
|
|
- if (foundIndex >= 0) {
|
|
|
|
|
- this.currentIndex = foundIndex;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `Song changed from UnifiedPlayerService: ${song.name}, index: ${this.currentIndex}`);
|
|
|
|
|
-
|
|
|
|
|
- // 同步到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- // 广播歌曲变化
|
|
|
|
|
- this.broadcastSongChange();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle song change: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onProgressChanged(progress: PlayProgress): void {
|
|
|
|
|
- try {
|
|
|
|
|
- this.playState.currentPosition = progress.currentPosition;
|
|
|
|
|
- this.playState.duration = progress.duration;
|
|
|
|
|
-
|
|
|
|
|
- // 节流广播进度更新
|
|
|
|
|
- this.broadcastProgressIfNeeded();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle progress change: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onError(error: PlayerError): void {
|
|
|
|
|
- try {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Player error from UnifiedPlayerService: ${error.type} - ${error.message}`);
|
|
|
|
|
-
|
|
|
|
|
- // 更新状态为错误状态
|
|
|
|
|
- this.playState.isPlaying = false;
|
|
|
|
|
- this.playState.isPaused = true;
|
|
|
|
|
- this.playState.isLoading = false;
|
|
|
|
|
-
|
|
|
|
|
- // 同步到主应用
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
-
|
|
|
|
|
- // 广播状态变化
|
|
|
|
|
- this.broadcastPlayerState();
|
|
|
|
|
-
|
|
|
|
|
- // 使用错误恢复策略处理错误
|
|
|
|
|
- this.handleUnifiedServiceError(error);
|
|
|
|
|
- } catch (handlingError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to handle player error: ${handlingError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理来自UnifiedPlayerService的错误
|
|
|
|
|
- */
|
|
|
|
|
- private async handleUnifiedServiceError(error: PlayerError): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 创建错误上下文
|
|
|
|
|
- const context: ErrorContext = {
|
|
|
|
|
- currentSong: this.currentSong,
|
|
|
|
|
- playlist: this.playlist,
|
|
|
|
|
- currentIndex: this.currentIndex,
|
|
|
|
|
- retryCount: error.retryCount,
|
|
|
|
|
- isNetworkAvailable: true
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 获取恢复策略
|
|
|
|
|
- const recoveryAction = await this.errorRecovery.handleError(error, context);
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, `UnifiedService error recovery action: ${recoveryAction}`);
|
|
|
|
|
-
|
|
|
|
|
- // 执行恢复动作
|
|
|
|
|
- switch (recoveryAction) {
|
|
|
|
|
- case ErrorRecoveryAction.RETRY:
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- setTimeout(async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.unifiedPlayerService.startPlayOrResumePlay();
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (retryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `UnifiedService retry failed: ${retryError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }, this.errorRecovery.getRetryDelay(error.retryCount));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.SKIP_TO_NEXT:
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.unifiedPlayerService.playNext();
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (skipError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `UnifiedService skip failed: ${skipError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case ErrorRecoveryAction.STOP_PLAYBACK:
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- try {
|
|
|
|
|
- await this.unifiedPlayerService.stop();
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- } catch (stopError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `UnifiedService stop failed: ${stopError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- hilog.warn(0x0000, TAG, `Unhandled recovery action for UnifiedService error: ${recoveryAction}`);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (recoveryError) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `UnifiedService error recovery failed: ${recoveryError}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 增强的状态同步机制
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 启动状态同步监听
|
|
|
|
|
- */
|
|
|
|
|
- private startStateSyncMonitoring(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- // 定期检查与UnifiedPlayerService的状态同步
|
|
|
|
|
- setInterval(() => {
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- this.checkStateSyncWithUnifiedService();
|
|
|
|
|
- }
|
|
|
|
|
- }, 2000); // 每2秒检查一次
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'State sync monitoring started');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to start state sync monitoring: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 检查与UnifiedPlayerService的状态同步
|
|
|
|
|
- */
|
|
|
|
|
- private checkStateSyncWithUnifiedService(): void {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!this.isUsingUnifiedService) return;
|
|
|
|
|
-
|
|
|
|
|
- const unifiedState = this.unifiedPlayerService.getCurrentState();
|
|
|
|
|
- const unifiedSong = this.unifiedPlayerService.getCurrentSong();
|
|
|
|
|
- const unifiedPlaylist = this.unifiedPlayerService.getPlaylist();
|
|
|
|
|
-
|
|
|
|
|
- // 检查播放状态是否同步
|
|
|
|
|
- if (unifiedState.isPlaying !== this.playState.isPlaying ||
|
|
|
|
|
- unifiedState.isPaused !== this.playState.isPaused) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Detected state desync, syncing from UnifiedPlayerService');
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查当前歌曲是否同步
|
|
|
|
|
- if (unifiedSong && (!this.currentSong || unifiedSong.id !== this.currentSong.id)) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Detected song desync, syncing from UnifiedPlayerService');
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查播放列表是否同步
|
|
|
|
|
- if (unifiedPlaylist.length !== this.playlist.length) {
|
|
|
|
|
- hilog.info(0x0000, TAG, 'Detected playlist desync, syncing from UnifiedPlayerService');
|
|
|
|
|
- this.syncFromUnifiedService();
|
|
|
|
|
- this.syncStateToMainApp();
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to check state sync: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 销毁服务
|
|
|
|
|
- */
|
|
|
|
|
- async destroy(): Promise<void> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 停止进度更新定时器
|
|
|
|
|
- this.stopProgressTask();
|
|
|
|
|
-
|
|
|
|
|
- // 移除UnifiedPlayerService监听器
|
|
|
|
|
- if (this.isUsingUnifiedService) {
|
|
|
|
|
- this.unifiedPlayerService.removeStateListener(this);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (this.ijkPlayer) {
|
|
|
|
|
- this.ijkPlayer.release();
|
|
|
|
|
- this.ijkPlayer = null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- emitter.off(9001);
|
|
|
|
|
- this.isInitialized = false;
|
|
|
|
|
-
|
|
|
|
|
- hilog.info(0x0000, TAG, 'IndependentPlayerService destroyed');
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- hilog.error(0x0000, TAG, `Failed to destroy service: ${error}`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|