|
@@ -588,6 +588,20 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
// WebDAV认证信息缓存(作为实例变量,比全局上下文更可靠)
|
|
// WebDAV认证信息缓存(作为实例变量,比全局上下文更可靠)
|
|
|
private currentWebDavAuthInfo: WebDavAuthItem | null = null;
|
|
private currentWebDavAuthInfo: WebDavAuthItem | null = null;
|
|
|
|
|
+ // 当前实际播放中的记忆key,避免切歌时 currentSong 已切换导致串写
|
|
|
|
|
+ private activePlaybackMemoryKey: string = '';
|
|
|
|
|
+ // 网盘流地址记忆恢复token,切歌后用于取消旧的延迟seek任务
|
|
|
|
|
+ private remoteRestoreSeekToken: number = 0;
|
|
|
|
|
+ // 本次是否需要先seek到记忆点再开始播放
|
|
|
|
|
+ private waitInitialSeekBeforeStart: boolean = false;
|
|
|
|
|
+ // 本次播放记忆点(毫秒)
|
|
|
|
|
+ private pendingMemorySeekPosition: number = 0;
|
|
|
|
|
+ // 记忆播放seek兜底,避免一直转圈
|
|
|
|
|
+ private initialSeekGuardTimer: number = 0;
|
|
|
|
|
+ private readonly initialSeekGuardTimeoutMs: number = 2000;
|
|
|
|
|
+ private readonly remoteMemorySeekInitialDelayMs: number = 120;
|
|
|
|
|
+ private readonly remoteMemorySeekRetryIntervalMs: number = 80;
|
|
|
|
|
+ private readonly remoteMemorySeekMaxRetryCount: number = 6;
|
|
|
|
|
|
|
|
//瀑布流的列数横竖屏动态切换
|
|
//瀑布流的列数横竖屏动态切换
|
|
|
onIsLandscapeChange() {
|
|
onIsLandscapeChange() {
|
|
@@ -2057,6 +2071,7 @@ export struct LocalMusic {
|
|
|
clearTimeout(this.mediaLibraryWarmupPrefetchTimer);
|
|
clearTimeout(this.mediaLibraryWarmupPrefetchTimer);
|
|
|
this.mediaLibraryWarmupPrefetchTimer = 0;
|
|
this.mediaLibraryWarmupPrefetchTimer = 0;
|
|
|
}
|
|
}
|
|
|
|
|
+ this.clearInitialSeekGuardTimer();
|
|
|
this.alphaBetBuildVersion++;
|
|
this.alphaBetBuildVersion++;
|
|
|
this.gridWaterLastActiveTs = 0;
|
|
this.gridWaterLastActiveTs = 0;
|
|
|
this.coverThumbCache.dispose();
|
|
this.coverThumbCache.dispose();
|
|
@@ -4228,7 +4243,6 @@ export struct LocalMusic {
|
|
|
}
|
|
}
|
|
|
.padding({ top: this.topSafeHeight+10, left: 10, right: 10 })
|
|
.padding({ top: this.topSafeHeight+10, left: 10, right: 10 })
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
- .visibility(Visibility.Visible)
|
|
|
|
|
.translate({
|
|
.translate({
|
|
|
x: 0,
|
|
x: 0,
|
|
|
y: (this.isShowTitleBar || !this.autoHideTitle) ? 0 : -80
|
|
y: (this.isShowTitleBar || !this.autoHideTitle) ? 0 : -80
|
|
@@ -12495,7 +12509,7 @@ export struct LocalMusic {
|
|
|
MenuImageBuilder(cover: string|undefined) {
|
|
MenuImageBuilder(cover: string|undefined) {
|
|
|
Menu(){
|
|
Menu(){
|
|
|
MenuItem({
|
|
MenuItem({
|
|
|
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')),
|
|
|
|
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.picture_2')),
|
|
|
content: '设为壁纸'
|
|
content: '设为壁纸'
|
|
|
})
|
|
})
|
|
|
.onClick(async() => {
|
|
.onClick(async() => {
|
|
@@ -12512,7 +12526,7 @@ export struct LocalMusic {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
MenuItem({
|
|
MenuItem({
|
|
|
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.arrow_up_to_line')),
|
|
|
|
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.picture')),
|
|
|
content: '更换封面'
|
|
content: '更换封面'
|
|
|
})
|
|
})
|
|
|
.onClick(async() => {
|
|
.onClick(async() => {
|
|
@@ -14893,10 +14907,17 @@ export struct LocalMusic {
|
|
|
if (this.CONTROL_PlayStatus == PlayStatus.INIT) {
|
|
if (this.CONTROL_PlayStatus == PlayStatus.INIT) {
|
|
|
this.stopProgressTask();
|
|
this.stopProgressTask();
|
|
|
this.startProgressTask();
|
|
this.startProgressTask();
|
|
|
|
|
+ this.clearInitialSeekGuardTimer();
|
|
|
|
|
+ this.activePlaybackMemoryKey = this.getPlaybackMemoryKey();
|
|
|
|
|
+ this.remoteRestoreSeekToken++;
|
|
|
|
|
+ const shouldMemoryPlay = PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false)
|
|
|
|
|
+ || (this.isMemoryLastPlay && this.isFirstStartPlay);
|
|
|
|
|
+ this.pendingMemorySeekPosition = shouldMemoryPlay ? PreferencesUtil.getNumberSync(this.getPlaybackMemoryKey(), 0) : 0;
|
|
|
|
|
+ const isRemoteStreamSong = this.currentSong && isRemoteCloudType(this.currentSong.type) &&
|
|
|
|
|
+ this.videoUrl.includes('://');
|
|
|
|
|
+ this.waitInitialSeekBeforeStart = !!isRemoteStreamSong && this.pendingMemorySeekPosition > 0;
|
|
|
// 判断是否是HTTP或在线网盘视频,显示加载进度
|
|
// 判断是否是HTTP或在线网盘视频,显示加载进度
|
|
|
- if (this.currentSong && isRemoteCloudType(this.currentSong.type)) {
|
|
|
|
|
- this.isPlayerLoading = true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.isPlayerLoading = this.waitInitialSeekBeforeStart;
|
|
|
this.play(this.videoUrl.toString(),startOffset);
|
|
this.play(this.videoUrl.toString(),startOffset);
|
|
|
}
|
|
}
|
|
|
if (this.CONTROL_PlayStatus == PlayStatus.PAUSE) {
|
|
if (this.CONTROL_PlayStatus == PlayStatus.PAUSE) {
|
|
@@ -15127,6 +15148,57 @@ export struct LocalMusic {
|
|
|
this.replayVisible = Visibility.Visible;
|
|
this.replayVisible = Visibility.Visible;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private clearInitialSeekGuardTimer(): void {
|
|
|
|
|
+ if (this.initialSeekGuardTimer) {
|
|
|
|
|
+ clearTimeout(this.initialSeekGuardTimer);
|
|
|
|
|
+ this.initialSeekGuardTimer = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private ensurePlayerStartedAfterInitialSeek(reason: string): void {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!this.mIjkMediaPlayer.isPlaying()) {
|
|
|
|
|
+ this.mIjkMediaPlayer.start();
|
|
|
|
|
+ }
|
|
|
|
|
+ this.CONTROL_PlayStatus = PlayStatus.PLAY;
|
|
|
|
|
+ this.setIsPlaying(true);
|
|
|
|
|
+ this.updateSessionPlayState(true);
|
|
|
|
|
+ Logger.info(TAG, `记忆播放恢复开播: ${reason}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `记忆播放恢复开播失败(${reason}): ${(error as Error).message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private finishInitialSeekWait(reason: string, shouldStart: boolean): void {
|
|
|
|
|
+ this.clearInitialSeekGuardTimer();
|
|
|
|
|
+ const wasWaiting = this.waitInitialSeekBeforeStart;
|
|
|
|
|
+ this.waitInitialSeekBeforeStart = false;
|
|
|
|
|
+ this.pendingMemorySeekPosition = 0;
|
|
|
|
|
+ this.isPlayerLoading = false;
|
|
|
|
|
+ this.hideLoadIng();
|
|
|
|
|
+ if (wasWaiting) {
|
|
|
|
|
+ Logger.info(TAG, `结束记忆seek等待: ${reason}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (shouldStart) {
|
|
|
|
|
+ this.ensurePlayerStartedAfterInitialSeek(reason);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private startInitialSeekGuardTimer(scene: string): void {
|
|
|
|
|
+ this.clearInitialSeekGuardTimer();
|
|
|
|
|
+ if (!this.waitInitialSeekBeforeStart || this.pendingMemorySeekPosition <= 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const token = this.remoteRestoreSeekToken;
|
|
|
|
|
+ this.initialSeekGuardTimer = setTimeout(() => {
|
|
|
|
|
+ if (token !== this.remoteRestoreSeekToken || !this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Logger.warn(TAG, `记忆seek超时兜底开播, scene=${scene}, pos=${this.pendingMemorySeekPosition}`);
|
|
|
|
|
+ this.finishInitialSeekWait('seek-timeout', true);
|
|
|
|
|
+ }, this.initialSeekGuardTimeoutMs);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
|
|
|
private async play(url: string,startOffset?:number) {
|
|
private async play(url: string,startOffset?:number) {
|
|
|
this.lyricController.setLyric(null)
|
|
this.lyricController.setLyric(null)
|
|
@@ -15148,7 +15220,12 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
|
|
|
|
|
let that = this;
|
|
let that = this;
|
|
|
- that.showLoadIng();
|
|
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ that.showLoadIng();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ that.hideLoadIng();
|
|
|
|
|
+ }
|
|
|
|
|
+ const startOnPreparedOption = this.waitInitialSeekBeforeStart ? "0" : "1";
|
|
|
this.isOpenAB = false
|
|
this.isOpenAB = false
|
|
|
//设置XComponent回调的context
|
|
//设置XComponent回调的context
|
|
|
if (!!this.mContext) {
|
|
if (!!this.mContext) {
|
|
@@ -15243,7 +15320,7 @@ export struct LocalMusic {
|
|
|
// 缓冲和播放优化设置
|
|
// 缓冲和播放优化设置
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", "1024000"); // 增大缓冲区
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", "1024000"); // 增大缓冲区
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "50"); // 减少最小帧数
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "50"); // 减少最小帧数
|
|
|
- this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1"); // 预加载启动
|
|
|
|
|
|
|
+ this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", startOnPreparedOption); // 预加载启动
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0"); // 无缓冲播放
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0"); // 无缓冲播放
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "30000"); // 最大缓存30秒
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "30000"); // 最大缓存30秒
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1"); // 无限制收流
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1"); // 无限制收流
|
|
@@ -15290,7 +15367,7 @@ export struct LocalMusic {
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100"); // 设置最小缓冲帧数
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100"); // 设置最小缓冲帧数
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-frames", "1000"); // 设置最大缓冲
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-frames", "1000"); // 设置最大缓冲
|
|
|
//启动预加载
|
|
//启动预加载
|
|
|
- this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");
|
|
|
|
|
|
|
+ this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", startOnPreparedOption);
|
|
|
// 设置无缓冲,这是播放器的缓冲区,有数据就播放
|
|
// 设置无缓冲,这是播放器的缓冲区,有数据就播放
|
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");
|
|
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");
|
|
|
//跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步
|
|
//跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步
|
|
@@ -15337,15 +15414,19 @@ export struct LocalMusic {
|
|
|
LogUtils.getInstance()
|
|
LogUtils.getInstance()
|
|
|
.LOGI("setOnVideoSizeChangedListener-->go:" + width + "," + height + "," + sar_num + "," + sar_den);
|
|
.LOGI("setOnVideoSizeChangedListener-->go:" + width + "," + height + "," + sar_num + "," + sar_den);
|
|
|
|
|
|
|
|
- that.hideLoadIng();
|
|
|
|
|
|
|
+ if (!that.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ that.hideLoadIng();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
this.mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
|
|
this.mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
|
|
|
let mOnPreparedListener: OnPreparedListener = new ImplOnPreparedListener(
|
|
let mOnPreparedListener: OnPreparedListener = new ImplOnPreparedListener(
|
|
|
async () => {
|
|
async () => {
|
|
|
LogUtils.getInstance().LOGI("setOnPreparedListener-->go");
|
|
LogUtils.getInstance().LOGI("setOnPreparedListener-->go");
|
|
|
- // 隐藏加载进度
|
|
|
|
|
- this.isPlayerLoading = false;
|
|
|
|
|
|
|
+ if (!this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ // 无记忆seek时,prepared后可直接隐藏加载
|
|
|
|
|
+ this.isPlayerLoading = false;
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
//保存碰一碰分享的当前的歌曲
|
|
//保存碰一碰分享的当前的歌曲
|
|
|
AppStorage.setOrCreate('currentSong',this.currentSong) ;
|
|
AppStorage.setOrCreate('currentSong',this.currentSong) ;
|
|
@@ -15480,6 +15561,11 @@ export struct LocalMusic {
|
|
|
(this.videoUrl.toLowerCase().endsWith('.rmvb') || this.videoUrl.toLowerCase().endsWith('.flac'))) {
|
|
(this.videoUrl.toLowerCase().endsWith('.rmvb') || this.videoUrl.toLowerCase().endsWith('.flac'))) {
|
|
|
timeoutPlay = 400
|
|
timeoutPlay = 400
|
|
|
}
|
|
}
|
|
|
|
|
+ // 网盘流地址在prepare后立刻seek容易触发底层不稳定,统一延后记忆恢复
|
|
|
|
|
+ if (this.currentSong && isRemoteCloudType(this.currentSong.type) && this.videoUrl.includes('://')) {
|
|
|
|
|
+ timeoutPlay = Math.max(timeoutPlay,
|
|
|
|
|
+ this.waitInitialSeekBeforeStart ? this.remoteMemorySeekInitialDelayMs : 700);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
//跳过片头片尾关键代码,以下是跳过片头的关键代码
|
|
//跳过片头片尾关键代码,以下是跳过片头的关键代码
|
|
|
if (this.modeType === 0) { //文件夹
|
|
if (this.modeType === 0) { //文件夹
|
|
@@ -15506,23 +15592,39 @@ export struct LocalMusic {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
- this.duration = this.mIjkMediaPlayer.getDuration()
|
|
|
|
|
|
|
+ const playerDuration = this.mIjkMediaPlayer.getDuration()
|
|
|
|
|
+ if (playerDuration > 0) {
|
|
|
|
|
+ this.duration = playerDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ const memoryPosition = this.pendingMemorySeekPosition > 0 ?
|
|
|
|
|
+ this.pendingMemorySeekPosition : PreferencesUtil.getNumberSync(this.getPlaybackMemoryKey(), 0);
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart && memoryPosition > 0) {
|
|
|
|
|
+ let targetSeekPosition = memoryPosition;
|
|
|
|
|
+ if (this.isOpenJump) {
|
|
|
|
|
+ targetSeekPosition = Math.max(targetSeekPosition, this.jumpTopTime * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.pendingMemorySeekPosition = targetSeekPosition;
|
|
|
|
|
+ this.startInitialSeekGuardTimer('prepared-memory-seek');
|
|
|
|
|
+ Logger.info(TAG, `准备记忆播放seek: ${targetSeekPosition}ms, timeout=${timeoutPlay}ms`);
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (!this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.restorePlaybackPosition();
|
|
|
|
|
+ }, timeoutPlay);
|
|
|
|
|
+ this.isFirstStartPlay = false
|
|
|
|
|
+ this.saveLastPlayList()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (this.isOpenJump) { //用户是否设置跳过片头片尾
|
|
if (this.isOpenJump) { //用户是否设置跳过片头片尾
|
|
|
if (this.mIjkMediaPlayer != null) {
|
|
if (this.mIjkMediaPlayer != null) {
|
|
|
let jumpTopTimeMs = this.jumpTopTime*1000;
|
|
let jumpTopTimeMs = this.jumpTopTime*1000;
|
|
|
if (jumpTopTimeMs <= this.duration) {
|
|
if (jumpTopTimeMs <= this.duration) {
|
|
|
// 调用 seekTo 方法,跳到指定时间
|
|
// 调用 seekTo 方法,跳到指定时间
|
|
|
- if(PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY,true)){
|
|
|
|
|
- console.log('onecold jumpTopTime = '+jumpTopTimeMs);
|
|
|
|
|
- const position:number = PreferencesUtil.getNumberSync(this.videoUrl, 0);
|
|
|
|
|
- console.log('onecold position = '+position);
|
|
|
|
|
- if (position > jumpTopTimeMs) {
|
|
|
|
|
- jumpTopTimeMs = position
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
|
|
setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
|
|
|
if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) {
|
|
if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) {
|
|
|
- this.seekTo(this.jumpTopTime * 1000 + ""); // 跳到开头
|
|
|
|
|
|
|
+ this.seekTo(jumpTopTimeMs + ""); // 跳到开头或记忆进度中更靠后的位置
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}, timeoutPlay)
|
|
}, timeoutPlay)
|
|
@@ -15543,7 +15645,8 @@ export struct LocalMusic {
|
|
|
//记忆播放(每一首都会记忆播放)
|
|
//记忆播放(每一首都会记忆播放)
|
|
|
if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) {
|
|
if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) {
|
|
|
setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
|
|
setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
|
|
|
- if (PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false)) {
|
|
|
|
|
|
|
+ if (this.pendingMemorySeekPosition > 0 ||
|
|
|
|
|
+ PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false)) {
|
|
|
this.restorePlaybackPosition() //读取播放记忆功能
|
|
this.restorePlaybackPosition() //读取播放记忆功能
|
|
|
} else if (this.isMemoryLastPlay && this.isFirstStartPlay) {
|
|
} else if (this.isMemoryLastPlay && this.isFirstStartPlay) {
|
|
|
console.error('onecold 最后一首:读取播放记忆功能 = ' + this.isFirstStartPlay);
|
|
console.error('onecold 最后一首:读取播放记忆功能 = ' + this.isFirstStartPlay);
|
|
@@ -15565,6 +15668,9 @@ export struct LocalMusic {
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
let err: BusinessError = error as BusinessError;
|
|
let err: BusinessError = error as BusinessError;
|
|
|
console.error('onecold failed with err: ' + JSON.stringify(err));
|
|
console.error('onecold failed with err: ' + JSON.stringify(err));
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ this.finishInitialSeekWait('prepared-exception', true);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -15646,13 +15752,15 @@ export struct LocalMusic {
|
|
|
let mOnSeekCompleteListener: OnSeekCompleteListener = new ImplOnSeekCompleteListener(
|
|
let mOnSeekCompleteListener: OnSeekCompleteListener = new ImplOnSeekCompleteListener(
|
|
|
() => {
|
|
() => {
|
|
|
LogUtils.getInstance().LOGI("OnSeekCompleteListener-->go");
|
|
LogUtils.getInstance().LOGI("OnSeekCompleteListener-->go");
|
|
|
- console.info('onecold startPlayOrResumePlay 10643')
|
|
|
|
|
// seek 完成,延迟恢复播放,确保 ijkplayer 内部状态完全稳定
|
|
// seek 完成,延迟恢复播放,确保 ijkplayer 内部状态完全稳定
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
- // seek 完成,隐藏转圈圈并重置标志
|
|
|
|
|
- that.hideLoadIng();
|
|
|
|
|
|
|
+ const waitingInitialSeek = that.waitInitialSeekBeforeStart;
|
|
|
that.isSeekTo = false;
|
|
that.isSeekTo = false;
|
|
|
- that.startPlayOrResumePlay();
|
|
|
|
|
|
|
+ if (waitingInitialSeek) {
|
|
|
|
|
+ that.finishInitialSeekWait('seek-complete', true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ that.hideLoadIng();
|
|
|
|
|
+ }
|
|
|
}, 200); // 延迟200ms,确保ijkplayer内部状态稳定
|
|
}, 200); // 延迟200ms,确保ijkplayer内部状态稳定
|
|
|
|
|
|
|
|
}
|
|
}
|
|
@@ -15662,7 +15770,9 @@ export struct LocalMusic {
|
|
|
let mOnInfoListener: OnInfoListener = new ImplOnInfoListener(
|
|
let mOnInfoListener: OnInfoListener = new ImplOnInfoListener(
|
|
|
(what: number, extra: number) => {
|
|
(what: number, extra: number) => {
|
|
|
LogUtils.getInstance().LOGI("OnInfoListener-->go:" + what + "===" + extra);
|
|
LogUtils.getInstance().LOGI("OnInfoListener-->go:" + what + "===" + extra);
|
|
|
- that.hideLoadIng();
|
|
|
|
|
|
|
+ if (!that.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ that.hideLoadIng();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
this.mIjkMediaPlayer.setOnInfoListener(mOnInfoListener);
|
|
this.mIjkMediaPlayer.setOnInfoListener(mOnInfoListener);
|
|
@@ -15670,8 +15780,7 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
let mOnErrorListener: OnErrorListener = new ImplOnErrorListener(
|
|
let mOnErrorListener: OnErrorListener = new ImplOnErrorListener(
|
|
|
(what: number, extra: number) => {
|
|
(what: number, extra: number) => {
|
|
|
- // 隐藏加载进度
|
|
|
|
|
- this.isPlayerLoading = false;
|
|
|
|
|
|
|
+ this.finishInitialSeekWait('player-error', false);
|
|
|
this.stopProgressTask();
|
|
this.stopProgressTask();
|
|
|
console.info("heanup OnErrorListener-->go:" + what + "===" + extra + " this.videoUrl=" + this.videoUrl)
|
|
console.info("heanup OnErrorListener-->go:" + what + "===" + extra + " this.videoUrl=" + this.videoUrl)
|
|
|
console.info('heanup 播放错误,歌曲详情:' + JSON.stringify(this.currentSong))
|
|
console.info('heanup 播放错误,歌曲详情:' + JSON.stringify(this.currentSong))
|
|
@@ -16565,6 +16674,10 @@ export struct LocalMusic {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private stop() {
|
|
private stop() {
|
|
|
|
|
+ this.clearInitialSeekGuardTimer();
|
|
|
|
|
+ this.waitInitialSeekBeforeStart = false;
|
|
|
|
|
+ this.pendingMemorySeekPosition = 0;
|
|
|
|
|
+ this.isPlayerLoading = false;
|
|
|
this.savePlaybackPosition();
|
|
this.savePlaybackPosition();
|
|
|
this.CONTROL_PlayStatus = PlayStatus.INIT;
|
|
this.CONTROL_PlayStatus = PlayStatus.INIT;
|
|
|
this.mIjkMediaPlayer.stop();
|
|
this.mIjkMediaPlayer.stop();
|
|
@@ -16586,18 +16699,99 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
// 如果播放位置接近视频末尾,则保存 position 为 0
|
|
// 如果播放位置接近视频末尾,则保存 position 为 0
|
|
|
const playbackPosition = (duration - position < threshold) ? 0 : position;
|
|
const playbackPosition = (duration - position < threshold) ? 0 : position;
|
|
|
- PreferencesUtil.putSync(this.videoUrl, playbackPosition);
|
|
|
|
|
|
|
+ const memoryKey = this.activePlaybackMemoryKey || this.getPlaybackMemoryKey();
|
|
|
|
|
+ PreferencesUtil.putSync(memoryKey, playbackPosition);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private buildPlaybackMemoryKey(song?: VideoItem, playbackUrl?: string): string {
|
|
|
|
|
+ if (song && isRemoteCloudType(song.type)) {
|
|
|
|
|
+ const accountId = song.webdav_account_id || 'unknown';
|
|
|
|
|
+ const stablePath = song.remote_rel_path || song.id || song.baiduFsId || song.filePath || playbackUrl || '';
|
|
|
|
|
+ return `memory_play_remote:${song.type}:${accountId}:${stablePath}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ return playbackUrl || '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private getPlaybackMemoryKey(): string {
|
|
|
|
|
+ return this.buildPlaybackMemoryKey(this.currentSong, this.videoUrl);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ private restoreRemotePlaybackPositionSafely(position: number, token: number, retryCount: number = 0): void {
|
|
|
|
|
+ if (token !== this.remoteRestoreSeekToken) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.mIjkMediaPlayer || position <= 0) {
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ this.finishInitialSeekWait('invalid-memory-position', true);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+ const duration = this.getActiveDuration();
|
|
|
|
|
+ const currentPos = this.mIjkMediaPlayer.getCurrentPosition();
|
|
|
|
|
+ let safePosition = position;
|
|
|
|
|
+ if (duration > 0) {
|
|
|
|
|
+ safePosition = Math.max(0, Math.min(position, duration - 500));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ safePosition = Math.max(0, position);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (safePosition <= 0) {
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ this.finishInitialSeekWait('memory-position-too-small', true);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (retryCount === 0 && duration <= 0 && currentPos <= 0) {
|
|
|
|
|
+ Logger.info(TAG, `远程记忆seek提前尝试: pos=${safePosition}, duration=${duration}, current=${currentPos}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.seekTo(safePosition + "");
|
|
|
|
|
+
|
|
|
|
|
+ if (retryCount >= this.remoteMemorySeekMaxRetryCount) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (token !== this.remoteRestoreSeekToken || !this.waitInitialSeekBeforeStart || !this.mIjkMediaPlayer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const latestPos = this.mIjkMediaPlayer.getCurrentPosition();
|
|
|
|
|
+ if (Math.abs(latestPos - safePosition) <= 1500) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.restoreRemotePlaybackPositionSafely(position, token, retryCount + 1);
|
|
|
|
|
+ }, this.remoteMemorySeekRetryIntervalMs);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//获取记忆播放功能
|
|
//获取记忆播放功能
|
|
|
private restorePlaybackPosition() {
|
|
private restorePlaybackPosition() {
|
|
|
- const position: number = PreferencesUtil.getNumberSync(this.videoUrl, 0);
|
|
|
|
|
- if (this.mIjkMediaPlayer != null && position > 0) {
|
|
|
|
|
- this.seekTo(position + "");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const position: number = this.pendingMemorySeekPosition > 0 ?
|
|
|
|
|
+ this.pendingMemorySeekPosition : PreferencesUtil.getNumberSync(this.getPlaybackMemoryKey(), 0);
|
|
|
|
|
+ if (!this.mIjkMediaPlayer || position <= 0) {
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ this.finishInitialSeekWait('no-memory-position', true);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const isRemoteSong = this.currentSong ? isRemoteCloudType(this.currentSong.type) : false;
|
|
|
|
|
+ if (isRemoteSong && this.videoUrl.includes('://')) {
|
|
|
|
|
+ const token = this.remoteRestoreSeekToken;
|
|
|
|
|
+ this.restoreRemotePlaybackPositionSafely(position, token);
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+ const duration = this.getActiveDuration();
|
|
|
|
|
+ let safePosition = position;
|
|
|
|
|
+ if (duration > 0) {
|
|
|
|
|
+ safePosition = Math.max(0, Math.min(position, duration - 500));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ safePosition = Math.max(0, position);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (safePosition <= 0) {
|
|
|
|
|
+ if (this.waitInitialSeekBeforeStart) {
|
|
|
|
|
+ this.finishInitialSeekWait('memory-position-too-small', true);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.seekTo(safePosition + "");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private async seekTo(value: string) {
|
|
private async seekTo(value: string) {
|
|
@@ -16630,14 +16824,28 @@ export struct LocalMusic {
|
|
|
} else {
|
|
} else {
|
|
|
// 本地播放器模式
|
|
// 本地播放器模式
|
|
|
let seekPos = Number.parseInt(value);
|
|
let seekPos = Number.parseInt(value);
|
|
|
|
|
+ if (Number.isNaN(seekPos)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const duration = this.getActiveDuration();
|
|
const duration = this.getActiveDuration();
|
|
|
|
|
+ const isRemoteSong = this.currentSong ? isRemoteCloudType(this.currentSong.type) : false;
|
|
|
if (duration > 0) {
|
|
if (duration > 0) {
|
|
|
seekPos = Math.max(0, Math.min(seekPos, duration - 200));
|
|
seekPos = Math.max(0, Math.min(seekPos, duration - 200));
|
|
|
|
|
+ } else if (seekPos < 0) {
|
|
|
|
|
+ seekPos = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isRemoteSong && duration <= 0) {
|
|
|
|
|
+ Logger.info(TAG, `seekTo 远程歌曲时长未知,直接尝试seek: ${seekPos}ms`);
|
|
|
}
|
|
}
|
|
|
this.isSeekTo = true
|
|
this.isSeekTo = true
|
|
|
- this.mIjkMediaPlayer.seekTo(seekPos + "");
|
|
|
|
|
- this.setProgress()
|
|
|
|
|
- this.isSeekTo = false
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.mIjkMediaPlayer.seekTo(seekPos + "");
|
|
|
|
|
+ this.setProgress()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.error(TAG, `本地播放器seek失败: ${error}`);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.isSeekTo = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|