|
|
@@ -859,7 +859,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 立即更新基础元数据(使用解析的duration),后续在播放器准备好后会用实际duration更新
|
|
|
await this.updateAvSessionMetadata(song);
|
|
|
- this.restorePlaybackPosition(song);
|
|
|
+
|
|
|
+ // 注释掉此处的记忆播放恢复,统一在onPrepared中处理
|
|
|
+ // this.restorePlaybackPosition(song);
|
|
|
+
|
|
|
this.currentRetryCount = 0;
|
|
|
}
|
|
|
|
|
|
@@ -918,7 +921,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
|
|
|
// 保存当前播放位置
|
|
|
- this.savePlaybackPosition();
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.savePlaybackPosition(currentSong);
|
|
|
+ }
|
|
|
|
|
|
// 暂停播放器 - 确保先暂停播放器
|
|
|
this.playerManager.pausePlayback();
|
|
|
@@ -951,7 +957,12 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
async stop(): Promise<void> {
|
|
|
try {
|
|
|
- this.savePlaybackPosition();
|
|
|
+ // 保存当前歌曲的播放位置
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.savePlaybackPosition(currentSong);
|
|
|
+ }
|
|
|
+
|
|
|
this.playerManager.stopPlayback();
|
|
|
|
|
|
// 重要:停止时应该清除所有播放状态,不是暂停状态
|
|
|
@@ -978,13 +989,26 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
*/
|
|
|
private async stopSilently(): Promise<void> {
|
|
|
try {
|
|
|
- this.savePlaybackPosition();
|
|
|
+ // 静默停止播放,不保存播放位置(位置已经在外部保存)
|
|
|
this.playerManager.stopPlayback();
|
|
|
this.stopProgressTimer();
|
|
|
// 不调用 updatePlayingState,避免触发状态变化通知
|
|
|
|
|
|
} catch (error) {
|
|
|
- LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService stopSilently error: ${error}`);
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService stopSilentlyWithoutSave error: ${error}`);
|
|
|
+ throw new Error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async stopSilentlyWithoutSave(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 静默停止播放,不保存播放位置(位置已经在外部保存),也不触发回调
|
|
|
+ this.playerManager.stopPlaybackSilently();
|
|
|
+ this.stopProgressTimer();
|
|
|
+ // 不调用 updatePlayingState,避免触发状态变化通知
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService stopSilentlyWithoutSave error: ${error}`);
|
|
|
throw new Error;
|
|
|
}
|
|
|
}
|
|
|
@@ -1038,6 +1062,13 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // **关键修复:在移动播放列表位置之前先获取并保存当前歌曲的播放位置**
|
|
|
+ const oldSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (oldSong) {
|
|
|
+ this.savePlaybackPosition(oldSong);
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 Saved old song position before track change: ${oldSong.name}`);
|
|
|
+ }
|
|
|
+
|
|
|
const playMode = this.stateModel.getState().playMode;
|
|
|
let moved = false;
|
|
|
|
|
|
@@ -1067,8 +1098,8 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
this.stateModel.updateCurrentSong(newSong);
|
|
|
}
|
|
|
|
|
|
- // 静默停止当前播放
|
|
|
- await this.stopSilently();
|
|
|
+ // 静默停止当前播放(不再保存位置,因为已经在上面保存了)
|
|
|
+ await this.stopSilentlyWithoutSave();
|
|
|
|
|
|
// 直接播放新歌曲
|
|
|
await this.playNewSongDirectly(newSong);
|
|
|
@@ -2079,6 +2110,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 重置播放器 - 这会清除所有状态包括播放位置
|
|
|
ijkPlayer.reset();
|
|
|
+
|
|
|
+ // 清空当前加载的歌曲记录
|
|
|
+ this.currentlyLoadedSong = null;
|
|
|
|
|
|
// 重新设置音频模式 - 重置后需要重新设置
|
|
|
ijkPlayer.setAudioId('unifiedPlayer');
|
|
|
@@ -2103,6 +2137,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 设置数据源
|
|
|
ijkPlayer.setDataSource(song.filePath);
|
|
|
+
|
|
|
+ // 记录当前播放器加载的歌曲
|
|
|
+ this.currentlyLoadedSong = song;
|
|
|
+
|
|
|
LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: Set data source to ${song.filePath}`);
|
|
|
|
|
|
// 优化:异步设置HTTP请求头和播放速度,不阻塞主流程
|
|
|
@@ -2210,21 +2248,47 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private savePlaybackPosition(): void {
|
|
|
- const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
- if (ijkPlayer != null) {
|
|
|
- const position = ijkPlayer.getCurrentPosition();
|
|
|
- // ToastUtil.showToast('保存记忆播放 = ' + position)
|
|
|
- const duration = ijkPlayer.getDuration();
|
|
|
- const threshold = 5000; // 阈值,单位为毫秒(这里设为5秒)
|
|
|
+ // 记录当前播放器实际加载的歌曲
|
|
|
+ private currentlyLoadedSong: VideoItem | null = null;
|
|
|
|
|
|
- // 如果播放位置接近视频末尾,则保存 position 为 0
|
|
|
- const playbackPosition = (duration - position < threshold) ? 0 : position;
|
|
|
- const currentSong = this.playlistModel.getCurrentSong();
|
|
|
- if (currentSong != null) {
|
|
|
- PuraPreferencesUtil.putSync(currentSong.filePath, playbackPosition);
|
|
|
+ private savePlaybackPosition(targetSong?: VideoItem): void {
|
|
|
+ try {
|
|
|
+ // 检查用户是否启用了记忆播放功能
|
|
|
+ const isMusicMemoryPlay = PuraPreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false);
|
|
|
+ if (!isMusicMemoryPlay) {
|
|
|
+ return; // 用户未启用,直接返回
|
|
|
+ }
|
|
|
+
|
|
|
+ const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
+ let currentSong = targetSong;
|
|
|
+
|
|
|
+ // 如果没有传入具体歌曲,优先使用播放器当前加载的歌曲
|
|
|
+ if (!currentSong) {
|
|
|
+ currentSong = this.currentlyLoadedSong || this.playlistModel.getCurrentSong()!;
|
|
|
}
|
|
|
+
|
|
|
+ if (ijkPlayer != null && currentSong != null) {
|
|
|
+ const position = ijkPlayer.getCurrentPosition();
|
|
|
+ const duration = ijkPlayer.getDuration();
|
|
|
+ const threshold = 5000; // 阈值,单位为毫秒(这里设为5秒)
|
|
|
|
|
|
+ // 如果播放位置接近视频末尾,则保存 position 为 0
|
|
|
+ const playbackPosition = (duration - position < threshold) ? 0 : position;
|
|
|
+
|
|
|
+ // 使用filePath作为键,确保与恢复时一致
|
|
|
+ PuraPreferencesUtil.putSync(currentSong.filePath, playbackPosition);
|
|
|
+
|
|
|
+ // 同时保存到AppStorage,确保卡片也能访问(使用相同的键)
|
|
|
+ AppStorage.setOrCreate(`playback_${currentSong.filePath}`, playbackPosition);
|
|
|
+
|
|
|
+ // 获取调用栈信息用于调试
|
|
|
+ const stack = new Error().stack;
|
|
|
+ const caller = stack?.split('\n')[2]?.trim() || 'Unknown caller';
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 Saved playback position: ${playbackPosition}ms for ${currentSong.name} (filePath: ${currentSong.filePath}) - Called from: ${caller}`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 savePlaybackPosition error: ${error}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2414,7 +2478,21 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
this.dataPersistence.savePlayerState(stoppedState);
|
|
|
}
|
|
|
|
|
|
- this.savePlaybackPosition();
|
|
|
+ // 修复:只在播放位置有意义时才保存,避免在歌曲刚开始时覆盖记忆播放位置
|
|
|
+ const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
+ if (ijkPlayer && this.isPlayerPreparedForCurrentSong) {
|
|
|
+ const currentPosition = ijkPlayer.getCurrentPosition();
|
|
|
+ // 只有当播放位置大于2秒时才保存,避免覆盖记忆播放位置
|
|
|
+ if (currentPosition > 2000) {
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.savePlaybackPosition(currentSong);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: Skipping playback position save - position too small (${currentPosition}ms)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
this.syncPlaylistToStorage();
|
|
|
|
|
|
LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: Current state saved successfully`);
|
|
|
@@ -2451,14 +2529,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
* 播放列表同步完成回调
|
|
|
*/
|
|
|
onPlaylistSynced(playlist: VideoItem[], currentIndex: number, playMode: PlayMode): void {
|
|
|
- try {
|
|
|
- LogUtils.getInstance()
|
|
|
- .LOGI(`Heanup UnifiedPlayerService: Playlist synced - ${playlist.length} songs, index ${currentIndex}`);
|
|
|
|
|
|
- // 播放列表同步完成,无需额外通知
|
|
|
- } catch (error) {
|
|
|
- LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService onPlaylistSynced error: ${error}`);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -2651,7 +2722,6 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 更新桌面卡片
|
|
|
await this.updateAllForms(true); // 强制更新
|
|
|
- LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: Current state broadcasted manually');
|
|
|
} catch (error) {
|
|
|
LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService broadcastCurrentState error: ${error}`);
|
|
|
throw new Error;
|
|
|
@@ -2688,7 +2758,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
LogUtils.getInstance().LOGI(`onecold 与其他应用音频冲突 被截断点event: ${JSON.stringify(event)}`);
|
|
|
|
|
|
// 首先保存当前播放位置
|
|
|
- await this.savePlaybackPosition();
|
|
|
+ const currentSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (currentSong) {
|
|
|
+ this.savePlaybackPosition(currentSong);
|
|
|
+ }
|
|
|
|
|
|
switch (event.hintType) {
|
|
|
case InterruptHintType.INTERRUPT_HINT_PAUSE:
|
|
|
@@ -2794,19 +2867,14 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
// ==================== PlayerStateCallback 实现 ====================
|
|
|
|
|
|
onPrepared(): void {
|
|
|
- LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: Player prepared');
|
|
|
- this.stateModel.updateLoadingState(false);
|
|
|
-
|
|
|
// 标记播放器已为当前歌曲准备就绪
|
|
|
this.isPlayerPreparedForCurrentSong = true;
|
|
|
console.log("Heanup2 UnifiedPlayerService: onPrepared - 播放器已为当前歌曲准备就绪");
|
|
|
|
|
|
- // 执行跳过片头和记忆播放恢复逻辑
|
|
|
setTimeout(() => {
|
|
|
+ // 执行跳过片头和记忆播放恢复逻辑
|
|
|
this.checkAndHandleOnPreparedLogic();
|
|
|
- }, 200);
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
+ this.stateModel.updateLoadingState(false);
|
|
|
const ijkPlayer = this.playerManager.getIjkPlayer();
|
|
|
const isActuallyPlaying = ijkPlayer ? ijkPlayer.isPlaying() : false;
|
|
|
|
|
|
@@ -2846,6 +2914,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
* 包括跳过片头和记忆播放恢复
|
|
|
*/
|
|
|
private checkAndHandleOnPreparedLogic(): void {
|
|
|
+ LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: 跳过片头和记忆播放恢复 Checking onPrepared logic');
|
|
|
try {
|
|
|
const currentSong = this.playlistModel.getCurrentSong();
|
|
|
if (!currentSong) {
|
|
|
@@ -2860,6 +2929,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
const jumpSettings = this.loadJumpSettings(currentSong);
|
|
|
|
|
|
if (jumpSettings.isEnabled && jumpSettings.jumpTopTime > 0) {
|
|
|
+ console.log(`Heanup UnifiedPlayerService: Jump settings enabled: ${jumpSettings.jumpTopTime}s`)
|
|
|
if (jumpSettings.jumpTopTime * 1000 <= duration) {
|
|
|
// 需要跳过片头
|
|
|
if (videoUrl && !videoUrl.toLowerCase().endsWith('.ts')) {
|
|
|
@@ -2907,31 +2977,36 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
*/
|
|
|
private handleMemoryPlayback(currentSong: VideoItem, videoUrl: string): void {
|
|
|
try {
|
|
|
+ console.log(`Heanup 记忆播放功能: 处理记忆播放 for ${currentSong.name}`)
|
|
|
+
|
|
|
// 检查用户是否启用了记忆播放功能
|
|
|
const isMusicMemoryPlay: boolean = PuraPreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false);
|
|
|
const isMemoryLastPlay: boolean = PuraPreferencesUtil.getBooleanSync(SettingPage.iS_MEMORY_LAST_PLAY, false);
|
|
|
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能: Settings - isMusicMemoryPlay: ${isMusicMemoryPlay}, isMemoryLastPlay: ${isMemoryLastPlay}`);
|
|
|
+
|
|
|
// 只在首次启动时检查 isMemoryLastPlay
|
|
|
- // 这里我们假设如果是首次播放,会有特殊标记
|
|
|
if (!isMusicMemoryPlay && !isMemoryLastPlay) {
|
|
|
- LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: Memory play disabled, skipping restore');
|
|
|
+ LogUtils.getInstance().LOGI('Heanup 记忆播放功能 : Memory play disabled, skipping restore');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (videoUrl && !videoUrl.toLowerCase().endsWith('.ts')) {
|
|
|
- // 优先从 AppStorage 获取,如果没有则从 PreferencesUtil 获取
|
|
|
- let position: number = AppStorage.get<number>(`playback_${videoUrl}`) || 0;
|
|
|
- if (position === 0) {
|
|
|
- position = PuraPreferencesUtil.getNumberSync(videoUrl, 0);
|
|
|
- }
|
|
|
-
|
|
|
+ if (currentSong && currentSong.filePath && !currentSong.filePath.toLowerCase().endsWith('.ts')) {
|
|
|
+ // 使用 currentSong.filePath 作为键,确保与保存时一致
|
|
|
+ let position = PuraPreferencesUtil.getNumberSync(currentSong.filePath, 0);
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能: Retrieved position ${position}ms from key: ${currentSong.filePath}`);
|
|
|
+
|
|
|
if (position > 0) {
|
|
|
- LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restoring playback position: ${position}ms for ${currentSong.name}`);
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 : Restoring playback position: ${position}ms for ${currentSong.name}`);
|
|
|
this.seekTo(position.toString());
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 : No saved position found for ${currentSong.name} (key: ${currentSong.filePath})`);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能: Skipping restore - invalid song or .ts file: ${currentSong?.filePath}`);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error in handleMemoryPlayback: ${error}`);
|
|
|
+ LogUtils.getInstance().LOGI(`Heanup 记忆播放功能 : Error in handleMemoryPlayback: ${error}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2988,6 +3063,18 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
onPlaybackCompleted(): void {
|
|
|
LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: Playback completed');
|
|
|
+
|
|
|
+ // **关键修复:在手动切换歌曲时,不要保存播放位置,因为已经在 changeTrack 中保存了**
|
|
|
+ if (!this.isManualSongChange) {
|
|
|
+ // 只有在非手动切换时才保存当前歌曲的播放位置(如自然播放完成)
|
|
|
+ const completedSong = this.playlistModel.getCurrentSong();
|
|
|
+ if (completedSong) {
|
|
|
+ this.savePlaybackPosition(completedSong);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI('Heanup 记忆播放功能: Skipping save in onPlaybackCompleted due to manual song change');
|
|
|
+ }
|
|
|
+
|
|
|
this.stateModel.updatePlayingState(false);
|
|
|
this.stopProgressTimer();
|
|
|
|