|
|
@@ -0,0 +1,1190 @@
|
|
|
+import { VideoItem } from '../../viewmodel/VideoItem';
|
|
|
+import { PlayerManager, IPlayerManager, PlayerStateCallback } from './PlayerManager';
|
|
|
+import { PlayerStateModel, PlayerState, PlayerStateListener, PlayMode, PlayerError, PlayerErrorType } from './PlayerStateModel';
|
|
|
+import { PlaylistModel } from './PlaylistModel';
|
|
|
+import { PlayProgress } from '../widget/WidgetTypes';
|
|
|
+import { LogUtils } from '@ohos/ijkplayer';
|
|
|
+import { common } from '@kit.AbilityKit';
|
|
|
+import { PreferencesUtil, StrUtil } from '@pura/harmony-utils';
|
|
|
+import { DataPersistenceService, PlaylistSyncService, PlaylistSyncListener, IDataPersistenceService, PlaylistData } from './DataPersistenceService';
|
|
|
+import { StateSyncService, IStateSyncService, StateChangeCallback, WidgetControlCallback } from './StateSyncService';
|
|
|
+import { ErrorRecoveryStrategy, IErrorRecoveryStrategy, ErrorContext, ErrorRecoveryAction } from './ErrorRecoveryStrategy';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 统一播放器服务接口
|
|
|
+ * 提供统一的播放控制接口,替代LocalMusic中的播放器逻辑
|
|
|
+ */
|
|
|
+export interface IPlayerService {
|
|
|
+ // 播放控制 - 基于LocalMusic现有方法
|
|
|
+ startPlayOrResumePlay(): Promise<void>;
|
|
|
+ pause(): Promise<void>;
|
|
|
+ stop(): Promise<void>;
|
|
|
+ seekTo(position: string): Promise<void>;
|
|
|
+
|
|
|
+ // 播放列表控制 - 基于LocalMusic现有方法
|
|
|
+ playNext(): Promise<void>;
|
|
|
+ playPrevious(): Promise<void>;
|
|
|
+ playSongAtIndex(index: number): Promise<void>;
|
|
|
+
|
|
|
+ // 状态查询 - 兼容LocalMusic数据结构
|
|
|
+ getCurrentState(): PlayerState;
|
|
|
+ getCurrentSong(): VideoItem | null;
|
|
|
+ getPlaylist(): VideoItem[];
|
|
|
+ getCurrentIndex(): number;
|
|
|
+
|
|
|
+ // 播放模式控制 - 基于LocalMusic现有功能
|
|
|
+ setPlayMode(mode: number): void;
|
|
|
+ getPlayMode(): number;
|
|
|
+
|
|
|
+ // 音量和速度控制 - 基于LocalMusic现有功能
|
|
|
+ setVolume(volume: number): void;
|
|
|
+ setPlaybackSpeed(speed: number): void;
|
|
|
+
|
|
|
+ // 播放列表管理
|
|
|
+ setPlaylist(songs: VideoItem[], currentIndex?: number): void;
|
|
|
+ addToPlaylist(song: VideoItem, index?: number): void;
|
|
|
+ removeFromPlaylist(index: number): VideoItem | null;
|
|
|
+
|
|
|
+ // 事件监听
|
|
|
+ addStateListener(listener: PlayerStateListener): void;
|
|
|
+ removeStateListener(listener: PlayerStateListener): void;
|
|
|
+
|
|
|
+ // 初始化和清理
|
|
|
+ initialize(context: common.UIAbilityContext): Promise<void>;
|
|
|
+ release(): void;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 统一播放器服务实现
|
|
|
+ * 整合PlayerManager、PlayerStateModel和PlaylistModel
|
|
|
+ */
|
|
|
+export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListener, StateChangeCallback, WidgetControlCallback, PlayerStateCallback {
|
|
|
+ private static instance: UnifiedPlayerService | null = null;
|
|
|
+ private playerManager: IPlayerManager;
|
|
|
+ private stateModel: PlayerStateModel;
|
|
|
+ private playlistModel: PlaylistModel;
|
|
|
+ private dataPersistence: IDataPersistenceService;
|
|
|
+ private playlistSync: PlaylistSyncService;
|
|
|
+ private stateSync: IStateSyncService;
|
|
|
+ private errorRecovery: IErrorRecoveryStrategy;
|
|
|
+ private context: common.UIAbilityContext | null = null;
|
|
|
+ private progressTimer: number = -1;
|
|
|
+ private isInitialized: boolean = false;
|
|
|
+ private currentRetryCount: number = 0;
|
|
|
+
|
|
|
+ private constructor() {
|
|
|
+ this.playerManager = PlayerManager.getInstance();
|
|
|
+ this.stateModel = new PlayerStateModel();
|
|
|
+ this.playlistModel = new PlaylistModel();
|
|
|
+ this.dataPersistence = DataPersistenceService.getInstance();
|
|
|
+ this.playlistSync = PlaylistSyncService.getInstance();
|
|
|
+ this.stateSync = StateSyncService.getInstance();
|
|
|
+ this.errorRecovery = ErrorRecoveryStrategy.getInstance();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Instance created');
|
|
|
+ }
|
|
|
+
|
|
|
+ public static getInstance(): UnifiedPlayerService {
|
|
|
+ if (!UnifiedPlayerService.instance) {
|
|
|
+ UnifiedPlayerService.instance = new UnifiedPlayerService();
|
|
|
+ }
|
|
|
+ return UnifiedPlayerService.instance;
|
|
|
+ }
|
|
|
+async initialize(context: common.UIAbilityContext): Promise<void> {
|
|
|
+ if (this.isInitialized) {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Already initialized');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.context = context;
|
|
|
+
|
|
|
+ // 初始化各个服务
|
|
|
+ await this.playerManager.initialize(context);
|
|
|
+
|
|
|
+ // 设置播放器状态回调
|
|
|
+ this.playerManager.setStateCallback(this);
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: PlayerManager state callback set');
|
|
|
+
|
|
|
+ await this.dataPersistence.initialize(context);
|
|
|
+ await this.playlistSync.initialize(context);
|
|
|
+ await this.stateSync.initialize(context);
|
|
|
+
|
|
|
+ // 设置同步监听器
|
|
|
+ this.playlistSync.addSyncListener(this);
|
|
|
+ this.stateSync.subscribeToStateChanges(this);
|
|
|
+ this.stateSync.subscribeToWidgetControl(this);
|
|
|
+
|
|
|
+ // 设置状态模型监听器,自动广播状态变化
|
|
|
+ class StateListenerImpl implements PlayerStateListener {
|
|
|
+ private stateSync: IStateSyncService;
|
|
|
+
|
|
|
+ constructor(stateSync: IStateSyncService) {
|
|
|
+ this.stateSync = stateSync;
|
|
|
+ }
|
|
|
+
|
|
|
+ onStateChanged(state: PlayerState): void {
|
|
|
+ this.stateSync.broadcastState(state)
|
|
|
+ }
|
|
|
+ onSongChanged(song: VideoItem): void {
|
|
|
+ this.stateSync.broadcastSongChange(song)
|
|
|
+ }
|
|
|
+ onProgressChanged(progress: PlayProgress): void {
|
|
|
+ this.stateSync.broadcastProgress(progress)
|
|
|
+ }
|
|
|
+ onError(error: PlayerError): void {
|
|
|
+ LogUtils.getInstance().LOGI(`Player error: ${error.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const stateListener = new StateListenerImpl(this.stateSync);
|
|
|
+ this.stateModel.addStateListener(stateListener);
|
|
|
+
|
|
|
+ // 恢复保存的状态
|
|
|
+ await this.restorePersistedState();
|
|
|
+
|
|
|
+ this.setupProgressTimer();
|
|
|
+ this.isInitialized = true;
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Initialized successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService initialization error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放控制方法
|
|
|
+ async startPlayOrResumePlay(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (!currentSong) {
|
|
|
+ await this.handlePlaybackError(new Error('No song to play'), PlayerErrorType.PLAYBACK_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.stateModel.updateLoadingState(true);
|
|
|
+
|
|
|
+ const playerInstance = this.playerManager.getIjkPlayer();
|
|
|
+ if (!playerInstance) {
|
|
|
+ await this.handlePlaybackError(new Error('Player not initialized'), PlayerErrorType.INITIALIZATION_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是暂停状态,直接恢复播放
|
|
|
+ if (this.stateModel.getState().isPaused && !this.stateModel.getState().isLoading) {
|
|
|
+ await this.playerManager.startPlayOrResumePlay();
|
|
|
+ this.stateModel.updatePlayingState(true);
|
|
|
+ this.startProgressTimer();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文件是否存在(对于本地文件)
|
|
|
+ if (!currentSong.filePath.startsWith('http')) {
|
|
|
+ const fileExists = await this.checkFileExists(currentSong.filePath);
|
|
|
+ if (!fileExists) {
|
|
|
+ await this.handlePlaybackError(new Error(`File not found: ${currentSong.filePath}`), PlayerErrorType.FILE_NOT_FOUND);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 检查网络连接(对于网络资源)
|
|
|
+ const networkAvailable = await this.checkNetworkConnection();
|
|
|
+ if (!networkAvailable) {
|
|
|
+ await this.handlePlaybackError(new Error('Network not available'), PlayerErrorType.NETWORK_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置播放源和配置
|
|
|
+ await this.setupPlayerForSong(currentSong);
|
|
|
+
|
|
|
+ // 准备播放器
|
|
|
+ const preparedPlayer = this.playerManager.getIjkPlayer();
|
|
|
+ if (preparedPlayer) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Preparing async for ${currentSong.name}`);
|
|
|
+ preparedPlayer.prepareAsync();
|
|
|
+ // 注意:实际播放和状态更新会在 onPrepared 回调中开始
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复播放位置(如果有记忆播放功能)
|
|
|
+ this.restorePlaybackPosition(currentSong);
|
|
|
+
|
|
|
+ // 重置重试计数(播放成功)
|
|
|
+ this.currentRetryCount = 0;
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Started playing ${currentSong.name}`);
|
|
|
+ } catch (error) {
|
|
|
+ this.stateModel.updateLoadingState(false);
|
|
|
+ this.stateModel.updatePlayingState(false);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService startPlayOrResumePlay error: ${error}`);
|
|
|
+
|
|
|
+ // 使用错误恢复机制处理错误
|
|
|
+ await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async pause(): Promise<void> {
|
|
|
+ try {
|
|
|
+ this.savePlaybackPosition();
|
|
|
+ this.playerManager.pausePlayback();
|
|
|
+ this.stateModel.updatePlayingState(false);
|
|
|
+ this.stopProgressTimer();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback paused');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService pause error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async stop(): Promise<void> {
|
|
|
+ try {
|
|
|
+ this.savePlaybackPosition();
|
|
|
+ this.playerManager.stopPlayback();
|
|
|
+ this.stateModel.updatePlayingState(false);
|
|
|
+ this.stateModel.updateProgress(0, 0);
|
|
|
+ this.stopProgressTimer();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback stopped');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService stop error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ async seekTo(position: string): Promise<void> {
|
|
|
+ try {
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (!currentSong) {
|
|
|
+ throw new Error('No song to seek');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否支持拖动(wma格式不支持)
|
|
|
+ if (StrUtil.isNotEmpty(currentSong.filePath) &&
|
|
|
+ currentSong.filePath.toLowerCase().endsWith('.wma')) {
|
|
|
+ throw new Error('WMA format does not support seeking');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.playerManager.seekToPosition(position);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Seeked to position ${position}`);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService seekTo error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放列表控制方法
|
|
|
+ async playNext(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+
|
|
|
+ if (!this.playlistModel.hasNext(playMode)) {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: No next song available');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const moved = this.playlistModel.moveToNext(playMode);
|
|
|
+ if (!moved) {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Failed to move to next song');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.stateModel.updateCurrentIndex(this.playlistModel.getCurrentIndex());
|
|
|
+
|
|
|
+ // 停止当前播放并开始新歌曲
|
|
|
+ await this.stop();
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 通知歌曲变化
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.stateModel.updateCurrentSong(currentSong);
|
|
|
+ }
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Moved to next song');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService playNext error: ${error}`);
|
|
|
+ await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async playPrevious(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+
|
|
|
+ if (!this.playlistModel.hasPrevious(playMode)) {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: No previous song available');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const moved = this.playlistModel.moveToPrevious(playMode);
|
|
|
+ if (!moved) {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Failed to move to previous song');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.stateModel.updateCurrentIndex(this.playlistModel.getCurrentIndex());
|
|
|
+
|
|
|
+ // 停止当前播放并开始新歌曲
|
|
|
+ await this.stop();
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 通知歌曲变化
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.stateModel.updateCurrentSong(currentSong);
|
|
|
+ }
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Moved to previous song');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService playPrevious error: ${error}`);
|
|
|
+ await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ async playSongAtIndex(index: number): Promise<void> {
|
|
|
+ try {
|
|
|
+ const success = this.playlistModel.playSongAtIndex(index);
|
|
|
+ if (!success) {
|
|
|
+ throw new Error(`Invalid song index: ${index}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.stateModel.updateCurrentIndex(index);
|
|
|
+
|
|
|
+ // 停止当前播放并开始新歌曲
|
|
|
+ await this.stop();
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 通知歌曲变化
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.stateModel.updateCurrentSong(currentSong);
|
|
|
+ }
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playing song at index ${index}`);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService playSongAtIndex error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 状态查询方法
|
|
|
+ getCurrentState(): PlayerState {
|
|
|
+ return this.stateModel.getState();
|
|
|
+ }
|
|
|
+
|
|
|
+ getCurrentSong(): VideoItem | null {
|
|
|
+ return this.playlistModel.getCurrentSong();
|
|
|
+ }
|
|
|
+
|
|
|
+ getPlaylist(): VideoItem[] {
|
|
|
+ return this.playlistModel.getSongs();
|
|
|
+ }
|
|
|
+
|
|
|
+ getCurrentIndex(): number {
|
|
|
+ return this.playlistModel.getCurrentIndex();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放模式控制
|
|
|
+ setPlayMode(mode: number): void {
|
|
|
+ const playMode = mode as PlayMode;
|
|
|
+ this.stateModel.updatePlayMode(playMode);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Play mode set to ${mode}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ getPlayMode(): number {
|
|
|
+ return this.stateModel.getState().playMode;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 音量和速度控制
|
|
|
+ setVolume(volume: number): void {
|
|
|
+ const volumeStr = volume.toString();
|
|
|
+ this.playerManager.setVolume(volumeStr, volumeStr);
|
|
|
+ this.stateModel.updateVolume(volume);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Volume set to ${volume}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ setPlaybackSpeed(speed: number): void {
|
|
|
+ const speedStr = speed.toString() + 'f';
|
|
|
+ this.playerManager.setPlaybackSpeed(speedStr);
|
|
|
+ this.stateModel.updateSpeed(speed);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Speed set to ${speed}`);
|
|
|
+ }
|
|
|
+ // 播放列表管理
|
|
|
+ setPlaylist(songs: VideoItem[], currentIndex: number = 0): void {
|
|
|
+ this.playlistModel.replaceSongs(songs, currentIndex);
|
|
|
+ this.stateModel.updateCurrentIndex(currentIndex);
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 更新当前歌曲
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ this.stateModel.updateCurrentSong(currentSong);
|
|
|
+
|
|
|
+ // 同步到持久化存储
|
|
|
+ this.syncPlaylistToStorage();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playlist set with ${songs.length} songs`);
|
|
|
+ }
|
|
|
+
|
|
|
+ addToPlaylist(song: VideoItem, index?: number): void {
|
|
|
+ this.playlistModel.addSong(song, index);
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 同步到持久化存储
|
|
|
+ this.syncPlaylistToStorage();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Song added to playlist`);
|
|
|
+ }
|
|
|
+
|
|
|
+ removeFromPlaylist(index: number): VideoItem | null {
|
|
|
+ const removedSong = this.playlistModel.removeSong(index);
|
|
|
+ if (removedSong) {
|
|
|
+ this.stateModel.updateCurrentIndex(this.playlistModel.getCurrentIndex());
|
|
|
+
|
|
|
+ // 更新状态模型中的播放列表状态
|
|
|
+ this.updatePlaylistStateInModel();
|
|
|
+
|
|
|
+ // 更新当前歌曲
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ this.stateModel.updateCurrentSong(currentSong);
|
|
|
+
|
|
|
+ // 同步到持久化存储
|
|
|
+ this.syncPlaylistToStorage();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Song removed from playlist at index ${index}`);
|
|
|
+ }
|
|
|
+ return removedSong;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 事件监听
|
|
|
+ addStateListener(listener: PlayerStateListener): void {
|
|
|
+ this.stateModel.addStateListener(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ removeStateListener(listener: PlayerStateListener): void {
|
|
|
+ this.stateModel.removeStateListener(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理资源
|
|
|
+ release(): void {
|
|
|
+ try {
|
|
|
+ this.stopProgressTimer();
|
|
|
+
|
|
|
+ // 保存当前状态
|
|
|
+ this.saveCurrentState();
|
|
|
+
|
|
|
+ // 清理同步监听器
|
|
|
+ this.playlistSync.removeSyncListener(this);
|
|
|
+ this.playlistSync.release();
|
|
|
+ this.stateSync.unsubscribeFromStateChanges(this);
|
|
|
+ this.stateSync.unsubscribeFromWidgetControl(this);
|
|
|
+ this.stateSync.release();
|
|
|
+
|
|
|
+ this.playerManager.release();
|
|
|
+ this.playlistModel.clear();
|
|
|
+ this.isInitialized = false;
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Resources released');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService release error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 错误处理和恢复方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理播放错误
|
|
|
+ */
|
|
|
+ private async handlePlaybackError(error: Error, errorType: PlayerErrorType = PlayerErrorType.PLAYBACK_ERROR): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 创建播放器错误对象
|
|
|
+ const playerError = new PlayerError(errorType, error.message);
|
|
|
+ playerError.retryCount = this.currentRetryCount;
|
|
|
+
|
|
|
+ // 创建错误上下文
|
|
|
+ const context: ErrorContext = {
|
|
|
+ currentSong: this.playlistModel.getCurrentSong(),
|
|
|
+ playlist: this.playlistModel.getSongs(),
|
|
|
+ currentIndex: this.playlistModel.getCurrentIndex(),
|
|
|
+ retryCount: this.currentRetryCount,
|
|
|
+ isNetworkAvailable: true // 这里可以实际检查网络状态
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取恢复策略
|
|
|
+ const recoveryAction = await this.errorRecovery.handleError(playerError, context);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery action: ${recoveryAction}`);
|
|
|
+
|
|
|
+ // 执行恢复动作
|
|
|
+ await this.executeRecoveryAction(recoveryAction, playerError, context);
|
|
|
+
|
|
|
+ } catch (recoveryError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery failed: ${recoveryError}`);
|
|
|
+ this.stateModel.notifyError(new PlayerError(PlayerErrorType.PLAYBACK_ERROR, `Recovery failed: ${recoveryError.message}`));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行错误恢复动作
|
|
|
+ */
|
|
|
+ private async executeRecoveryAction(action: ErrorRecoveryAction, error: PlayerError, context: ErrorContext): Promise<void> {
|
|
|
+ switch (action) {
|
|
|
+ case ErrorRecoveryAction.RETRY:
|
|
|
+ await this.retryCurrentOperation(error);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ErrorRecoveryAction.SKIP_TO_NEXT:
|
|
|
+ await this.skipToNextSong();
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ErrorRecoveryAction.STOP_PLAYBACK:
|
|
|
+ await this.stop();
|
|
|
+ this.stateModel.notifyError(error);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ErrorRecoveryAction.WAIT_AND_RETRY:
|
|
|
+ await this.waitAndRetry(error);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ErrorRecoveryAction.SHOW_ERROR:
|
|
|
+ this.stateModel.notifyError(error);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Unknown recovery action: ${action}`);
|
|
|
+ this.stateModel.notifyError(error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重试当前操作
|
|
|
+ */
|
|
|
+ private async retryCurrentOperation(error: PlayerError): Promise<void> {
|
|
|
+ try {
|
|
|
+ this.currentRetryCount++;
|
|
|
+ const delay = this.errorRecovery.getRetryDelay(this.currentRetryCount);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Retrying operation in ${delay}ms (attempt ${this.currentRetryCount})`);
|
|
|
+
|
|
|
+ // 等待指定时间后重试
|
|
|
+ setTimeout(async () => {
|
|
|
+ try {
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+ this.currentRetryCount = 0; // 重试成功,重置计数
|
|
|
+ } catch (retryError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Retry failed: ${retryError}`);
|
|
|
+ await this.handlePlaybackError(retryError as Error, error.type);
|
|
|
+ }
|
|
|
+ }, delay);
|
|
|
+
|
|
|
+ } catch (retryError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Retry setup failed: ${retryError}`);
|
|
|
+ await this.skipToNextSong();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 跳到下一首歌曲
|
|
|
+ */
|
|
|
+ private async skipToNextSong(): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Skipping to next song due to error');
|
|
|
+
|
|
|
+ // 重置重试计数
|
|
|
+ this.currentRetryCount = 0;
|
|
|
+
|
|
|
+ // 检查是否有下一首
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+ if (this.playlistModel.hasNext(playMode)) {
|
|
|
+ await this.playNext();
|
|
|
+ } else {
|
|
|
+ // 没有下一首,停止播放
|
|
|
+ await this.stop();
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: No next song available, stopping playback');
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (skipError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to skip to next song: ${skipError}`);
|
|
|
+ await this.stop();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 等待后重试
|
|
|
+ */
|
|
|
+ private async waitAndRetry(error: PlayerError): Promise<void> {
|
|
|
+ try {
|
|
|
+ this.currentRetryCount++;
|
|
|
+ const delay = this.errorRecovery.getRetryDelay(this.currentRetryCount);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Waiting ${delay}ms before retry (network error)`);
|
|
|
+
|
|
|
+ // 更新状态为加载中
|
|
|
+ this.stateModel.updateLoadingState(true);
|
|
|
+
|
|
|
+ setTimeout(async () => {
|
|
|
+ try {
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+ this.currentRetryCount = 0; // 重试成功,重置计数
|
|
|
+ } catch (retryError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Network retry failed: ${retryError}`);
|
|
|
+ await this.handlePlaybackError(retryError as Error, PlayerErrorType.NETWORK_ERROR);
|
|
|
+ }
|
|
|
+ }, delay);
|
|
|
+
|
|
|
+ } catch (waitError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Wait and retry setup failed: ${waitError}`);
|
|
|
+ await this.skipToNextSong();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查文件是否存在
|
|
|
+ */
|
|
|
+ private async checkFileExists(filePath: string): Promise<boolean> {
|
|
|
+ try {
|
|
|
+ // 这里可以添加文件存在性检查逻辑
|
|
|
+ // 暂时返回true,实际实现需要使用文件系统API
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: File check failed: ${error}`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查网络连接
|
|
|
+ */
|
|
|
+ private async checkNetworkConnection(): Promise<boolean> {
|
|
|
+ try {
|
|
|
+ // 这里可以添加网络连接检查逻辑
|
|
|
+ // 暂时返回true,实际实现需要使用网络API
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Network check failed: ${error}`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 私有辅助方法
|
|
|
+ private async setupPlayerForSong(song: VideoItem): Promise<void> {
|
|
|
+ const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
+ if (!ijkPlayer) {
|
|
|
+ throw new Error('Player not initialized');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置播放器
|
|
|
+ ijkPlayer.reset();
|
|
|
+
|
|
|
+ // 重新设置音频模式 - 重置后需要重新设置
|
|
|
+ ijkPlayer.setAudioId('unifiedPlayer');
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Reset audio mode for ${song.name}`);
|
|
|
+
|
|
|
+ // 重新设置配置
|
|
|
+ this.playerManager.setupIjkPlayerOptions();
|
|
|
+
|
|
|
+ // 设置音量(针对dsf格式特殊处理)
|
|
|
+ const volume = this.stateModel.getState().volume;
|
|
|
+ if (song.filePath.toLowerCase().endsWith('.dsf')) {
|
|
|
+ this.playerManager.setVolume('1', '1');
|
|
|
+ } else {
|
|
|
+ this.playerManager.setVolume(volume.toString(), volume.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置数据源
|
|
|
+ ijkPlayer.setDataSource(song.filePath);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Set data source to ${song.filePath}`);
|
|
|
+
|
|
|
+ // 设置HTTP请求头
|
|
|
+ const headers = new Map([
|
|
|
+ ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],
|
|
|
+ ["referer", "https://www.bilibili.com"]
|
|
|
+ ]);
|
|
|
+ ijkPlayer.setDataSourceHeader(headers);
|
|
|
+
|
|
|
+ // 设置播放速度
|
|
|
+ const speed = this.stateModel.getState().speed;
|
|
|
+ this.playerManager.setPlaybackSpeed(speed.toString() + 'f');
|
|
|
+
|
|
|
+ // 设置消息监听器(重要:用于处理播放器内部事件)
|
|
|
+ ijkPlayer.setMessageListener();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Player setup for song ${song.name}`);
|
|
|
+ }
|
|
|
+ private setupProgressTimer(): void {
|
|
|
+ this.progressTimer = setInterval(() => {
|
|
|
+ this.updateProgress();
|
|
|
+ }, 1000); // 每秒更新一次进度
|
|
|
+ }
|
|
|
+
|
|
|
+ private startProgressTimer(): void {
|
|
|
+ if (this.progressTimer === -1) {
|
|
|
+ this.setupProgressTimer();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private stopProgressTimer(): void {
|
|
|
+ if (this.progressTimer !== -1) {
|
|
|
+ clearInterval(this.progressTimer);
|
|
|
+ this.progressTimer = -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private updateProgress(): void {
|
|
|
+ try {
|
|
|
+ const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
+ if (!ijkPlayer || !this.stateModel.getState().isPlaying) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentPosition = ijkPlayer.getCurrentPosition();
|
|
|
+ const duration = ijkPlayer.getDuration();
|
|
|
+
|
|
|
+ this.stateModel.updateProgress(currentPosition, duration);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateProgress error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private savePlaybackPosition(): void {
|
|
|
+ try {
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
+
|
|
|
+ if (!currentSong || !ijkPlayer) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const position = ijkPlayer.getCurrentPosition();
|
|
|
+ const duration = ijkPlayer.getDuration();
|
|
|
+
|
|
|
+ // 使用DataPersistenceService保存播放进度
|
|
|
+ this.dataPersistence.savePlaybackProgress(
|
|
|
+ currentSong.filePath, // 使用filePath作为songId
|
|
|
+ currentSong.filePath,
|
|
|
+ position,
|
|
|
+ duration
|
|
|
+ )
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Saved playback position ${position}ms for ${currentSong.name}`);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService savePlaybackPosition error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private restorePlaybackPosition(song: VideoItem): void {
|
|
|
+ try {
|
|
|
+ // 使用DataPersistenceService恢复播放进度
|
|
|
+ this.dataPersistence.loadPlaybackProgress(song.filePath).then(progressData => {
|
|
|
+ if (progressData && progressData.position > 0 && !progressData.completed) {
|
|
|
+ this.playerManager.seekToPosition(progressData.position.toString());
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restored playback position ${progressData.position}ms for ${song.name}`);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService restorePlaybackPosition error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // ==================== 数据持久化和同步方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 恢复持久化的状态
|
|
|
+ */
|
|
|
+ private async restorePersistedState(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 恢复播放列表
|
|
|
+ const playlistData = await this.dataPersistence.loadPlaylist();
|
|
|
+ if (playlistData && playlistData.songs.length > 0) {
|
|
|
+ this.playlistModel.replaceSongs(playlistData.songs, playlistData.currentIndex);
|
|
|
+ this.stateModel.updateCurrentIndex(playlistData.currentIndex);
|
|
|
+ this.stateModel.updatePlayMode(playlistData.playMode);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restored playlist with ${playlistData.songs.length} songs`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复播放状态
|
|
|
+ const stateData = await this.dataPersistence.loadPlayerState();
|
|
|
+ if (stateData) {
|
|
|
+ this.stateModel.updateVolume(stateData.volume);
|
|
|
+ this.stateModel.updateSpeed(stateData.speed);
|
|
|
+ this.stateModel.updatePlayMode(stateData.playMode);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Restored player state');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService restorePersistedState error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存当前状态
|
|
|
+ */
|
|
|
+ private saveCurrentState(): void {
|
|
|
+ try {
|
|
|
+ // 保存播放状态
|
|
|
+ const currentState = this.stateModel.getState();
|
|
|
+ this.dataPersistence.savePlayerState(currentState)
|
|
|
+
|
|
|
+ // 保存播放进度
|
|
|
+ this.savePlaybackPosition();
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Current state saved');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService saveCurrentState error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步播放列表到存储
|
|
|
+ */
|
|
|
+ private syncPlaylistToStorage(): void {
|
|
|
+ try {
|
|
|
+ const playlist = this.playlistModel.getSongs();
|
|
|
+ const currentIndex = this.playlistModel.getCurrentIndex();
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+
|
|
|
+ this.playlistSync.syncPlaylistToStorage(playlist, currentIndex, playMode);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService syncPlaylistToStorage error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== PlaylistSyncListener 接口实现 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放列表同步完成回调
|
|
|
+ */
|
|
|
+ onPlaylistSynced(playlist: VideoItem[], currentIndex: number, playMode: PlayMode): void {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playlist synced - ${playlist.length} songs, index ${currentIndex}`);
|
|
|
+
|
|
|
+ // 播放列表同步完成,无需额外通知
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlaylistSynced error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放列表加载完成回调
|
|
|
+ */
|
|
|
+ onPlaylistLoaded(playlistData: PlaylistData): void {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playlist loaded from storage - ${playlistData.songs.length} songs`);
|
|
|
+
|
|
|
+ // 检查是否需要更新本地播放列表
|
|
|
+ const currentPlaylist = this.playlistModel.getSongs();
|
|
|
+ const currentIndex = this.playlistModel.getCurrentIndex();
|
|
|
+
|
|
|
+ // 如果存储的数据更新,更新本地播放列表
|
|
|
+ if (playlistData.songs.length !== currentPlaylist.length ||
|
|
|
+ playlistData.currentIndex !== currentIndex) {
|
|
|
+
|
|
|
+ this.playlistModel.replaceSongs(playlistData.songs, playlistData.currentIndex);
|
|
|
+ this.stateModel.updateCurrentIndex(playlistData.currentIndex);
|
|
|
+ this.stateModel.updatePlayMode(playlistData.playMode);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Local playlist updated from storage');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlaylistLoaded error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动同步执行回调
|
|
|
+ */
|
|
|
+ onAutoSyncPerformed(): void {
|
|
|
+ try {
|
|
|
+ // 定期保存当前状态
|
|
|
+ this.saveCurrentState();
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onAutoSyncPerformed error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步错误回调
|
|
|
+ */
|
|
|
+ onSyncError(error: Error): void {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService sync error: ${error.message}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 公共数据访问方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取数据持久化服务实例
|
|
|
+ */
|
|
|
+ getDataPersistenceService(): IDataPersistenceService {
|
|
|
+ return this.dataPersistence;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取播放列表同步服务实例
|
|
|
+ */
|
|
|
+ getPlaylistSyncService(): PlaylistSyncService {
|
|
|
+ return this.playlistSync;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手动触发数据同步
|
|
|
+ */
|
|
|
+ async manualSync(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const playlist = this.playlistModel.getSongs();
|
|
|
+ const currentIndex = this.playlistModel.getCurrentIndex();
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+
|
|
|
+ // 执行双向同步
|
|
|
+ const syncedData = await this.playlistSync.bidirectionalSync(playlist, currentIndex, playMode);
|
|
|
+
|
|
|
+ // 如果同步后的数据与本地不同,更新本地数据
|
|
|
+ if (syncedData.songs.length !== playlist.length ||
|
|
|
+ syncedData.currentIndex !== currentIndex ||
|
|
|
+ syncedData.playMode !== playMode) {
|
|
|
+
|
|
|
+ this.playlistModel.replaceSongs(syncedData.songs, syncedData.currentIndex);
|
|
|
+ this.stateModel.updateCurrentIndex(syncedData.currentIndex);
|
|
|
+ this.stateModel.updatePlayMode(syncedData.playMode);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Manual sync completed with data update');
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Manual sync completed - no changes');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService manualSync error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== StateChangeCallback 接口实现 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 状态变化回调(来自StateSyncService的本地通知)
|
|
|
+ */
|
|
|
+ onStateChanged?(state: PlayerState): void {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received state change notification - isPlaying=${state.isPlaying}`);
|
|
|
+ // 这里可以处理来自其他组件的状态变化通知
|
|
|
+ // 通常情况下,状态变化是由本服务发起的,所以这里主要用于调试和监控
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onStateChanged error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 歌曲变化回调(来自StateSyncService的本地通知)
|
|
|
+ */
|
|
|
+ onSongChanged?(song: VideoItem): void {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received song change notification - ${song.name}`);
|
|
|
+ // 这里可以处理来自其他组件的歌曲变化通知
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onSongChanged error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 进度变化回调(来自StateSyncService的本地通知)
|
|
|
+ */
|
|
|
+ onProgressChanged?(progress: PlayProgress): void {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received progress change notification - ${progress.percentage.toFixed(1)}%`);
|
|
|
+ // 这里可以处理来自其他组件的进度变化通知
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onProgressChanged error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== WidgetControlCallback 接口实现 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理来自卡片的播放/暂停命令
|
|
|
+ */
|
|
|
+ async onPlayPause?(): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Received play/pause command from widget');
|
|
|
+
|
|
|
+ const currentState = this.getCurrentState();
|
|
|
+ if (currentState.isPlaying) {
|
|
|
+ await this.pause();
|
|
|
+ } else {
|
|
|
+ await this.startPlayOrResumePlay();
|
|
|
+ }
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Play/pause command executed successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlayPause error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理来自卡片的下一首命令
|
|
|
+ */
|
|
|
+ async onNextSong?(): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Received next song command from widget');
|
|
|
+ await this.playNext();
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Next song command executed successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onNextSong error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理来自卡片的上一首命令
|
|
|
+ */
|
|
|
+ async onPreviousSong?(): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Received previous song command from widget');
|
|
|
+ await this.playPrevious();
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Previous song command executed successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onPreviousSong error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理来自卡片的拖动进度命令
|
|
|
+ */
|
|
|
+ async onSeekTo?(position: number): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received seek to command from widget - position: ${position}`);
|
|
|
+ await this.seekTo(position.toString());
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Seek to command executed successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onSeekTo error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理来自卡片的状态请求命令
|
|
|
+ */
|
|
|
+ async onStateRequest?(): Promise<void> {
|
|
|
+ try {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Received state request from widget');
|
|
|
+ await this.broadcastCurrentState();
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: State request handled successfully');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService onStateRequest error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 状态同步服务访问方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取状态同步服务实例
|
|
|
+ */
|
|
|
+ getStateSyncService(): IStateSyncService {
|
|
|
+ return this.stateSync;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手动广播当前状态(用于响应卡片的状态请求)
|
|
|
+ */
|
|
|
+ async broadcastCurrentState(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const currentState = this.getCurrentState();
|
|
|
+ await this.stateSync.broadcastState(currentState);
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Current state broadcasted manually');
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService broadcastCurrentState error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有辅助方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新状态模型中的播放列表状态
|
|
|
+ */
|
|
|
+ private updatePlaylistStateInModel(): void {
|
|
|
+ try {
|
|
|
+ const currentIndex = this.playlistModel.getCurrentIndex();
|
|
|
+ const totalCount = this.playlistModel.getTotalCount();
|
|
|
+ const playMode = this.stateModel.getState().playMode;
|
|
|
+
|
|
|
+ const hasNext = this.playlistModel.hasNext(playMode);
|
|
|
+ const hasPrevious = this.playlistModel.hasPrevious(playMode);
|
|
|
+
|
|
|
+ this.stateModel.updatePlaylistState(hasNext, hasPrevious, totalCount);
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playlist state updated - hasNext=${hasNext}, hasPrevious=${hasPrevious}, totalCount=${totalCount}`);
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updatePlaylistStateInModel error: ${error}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== PlayerStateCallback 实现 ====================
|
|
|
+
|
|
|
+ onPrepared(): void {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Player prepared');
|
|
|
+ this.stateModel.updateLoadingState(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ onPlaybackStarted(): void {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback started');
|
|
|
+ this.stateModel.updatePlayingState(true);
|
|
|
+ this.startProgressTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ onPlaybackCompleted(): void {
|
|
|
+ LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback completed');
|
|
|
+ this.stateModel.updatePlayingState(false);
|
|
|
+ this.stopProgressTimer();
|
|
|
+
|
|
|
+ // 自动播放下一首(如果有的话)
|
|
|
+ const currentPlayMode = this.stateModel.getState().playMode;
|
|
|
+ if (this.playlistModel.hasNext(currentPlayMode)) {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.playNext();
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onPlaybackError(what: number, extra: number): void {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playback error - what: ${what}, extra: ${extra}`);
|
|
|
+ this.stateModel.updateLoadingState(false);
|
|
|
+ this.stateModel.updatePlayingState(false);
|
|
|
+ this.stopProgressTimer();
|
|
|
+
|
|
|
+ // 创建播放器错误对象
|
|
|
+ const playbackError = new PlayerError(
|
|
|
+ PlayerErrorType.PLAYBACK_ERROR,
|
|
|
+ `Playback error: what=${what}, extra=${extra}`,
|
|
|
+ what
|
|
|
+ );
|
|
|
+ playbackError.recoverable = what !== -1004 && what !== -1007; // 某些错误码可恢复
|
|
|
+ playbackError.retryCount = 0;
|
|
|
+
|
|
|
+ // 创建错误上下文
|
|
|
+ const errorContext: ErrorContext = {
|
|
|
+ currentSong: this.getCurrentSong(),
|
|
|
+ playlist: this.getPlaylist(),
|
|
|
+ currentIndex: this.getCurrentIndex(),
|
|
|
+ retryCount: 0,
|
|
|
+ isNetworkAvailable: true // 这里可以实际检查网络状态
|
|
|
+ };
|
|
|
+
|
|
|
+ // 使用错误恢复策略处理错误
|
|
|
+ this.errorRecovery.handleError(playbackError, errorContext).then((action: ErrorRecoveryAction) => {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery action: ${action}`);
|
|
|
+ // 这里可以根据 action 执行相应的恢复操作
|
|
|
+ }).catch((recoveryError: Error) => {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery failed: ${recoveryError}`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|