|
|
@@ -12,6 +12,9 @@ import { ErrorRecoveryStrategy, IErrorRecoveryStrategy, ErrorContext, ErrorRecov
|
|
|
import { AvSessionController } from '../../controller/AvSessionController';
|
|
|
import { avSession } from '@kit.AVSessionKit';
|
|
|
import { formProvider, formBindingData } from '@kit.FormKit';
|
|
|
+import { fileIo } from '@kit.CoreFileKit';
|
|
|
+import { BusinessError } from '@kit.BasicServicesKit';
|
|
|
+import { http } from '@kit.NetworkKit';
|
|
|
import json from '@ohos.util.json';
|
|
|
import MediaTable from '../util/MediaTable';
|
|
|
|
|
|
@@ -94,6 +97,8 @@ export interface WidgetFormData {
|
|
|
playerState: WidgetPlayerState;
|
|
|
playlistInfo: WidgetPlaylistInfo;
|
|
|
timeInfo: WidgetTimeInfo;
|
|
|
+ // 卡片图片显示必填字段
|
|
|
+ formImages: Record<string, number>;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -336,6 +341,9 @@ interface widgeData {
|
|
|
playerState: WidgetPlayerState;
|
|
|
playlistInfo: WidgetPlaylistInfo;
|
|
|
timeInfo: WidgetTimeInfo;
|
|
|
+
|
|
|
+ // 卡片图片显示必填字段
|
|
|
+ formImages: Record<string, number>;
|
|
|
}
|
|
|
|
|
|
export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListener, PlayerStateCallback {
|
|
|
@@ -348,7 +356,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
private errorRecovery: IErrorRecoveryStrategy;
|
|
|
private avSessionController: AvSessionController | null = null;
|
|
|
private context: common.UIAbilityContext | null = null;
|
|
|
-
|
|
|
+
|
|
|
// 防重复调用机制
|
|
|
private isUpdatingForms: boolean = false;
|
|
|
private lastUpdateTime: number = 0;
|
|
|
@@ -586,7 +594,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
this.saveCurrentState();
|
|
|
this.updateSessionPlayState();
|
|
|
await this.updateWidgetsForPlayStateChange();
|
|
|
-
|
|
|
+
|
|
|
// 同步更新所有桌面卡片状态
|
|
|
await this.updateAllForms();
|
|
|
} catch (error) {
|
|
|
@@ -856,7 +864,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
try {
|
|
|
// 确保播放列表状态是最新的
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
-
|
|
|
+
|
|
|
// 强制更新所有桌面卡片状态
|
|
|
await this.updateAllForms(true);
|
|
|
if (newSong) {
|
|
|
@@ -1971,7 +1979,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 设置一个临时的数据恢复完成标识,允许快速响应
|
|
|
this.isDataRestored = true;
|
|
|
-
|
|
|
+
|
|
|
// 立即更新播放列表状态,确保上一首/下一首状态正确
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
} catch (error) {
|
|
|
@@ -2006,7 +2014,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
}
|
|
|
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restored playlist with ${playlistData.songs.length} songs`);
|
|
|
-
|
|
|
+
|
|
|
// 立即更新播放列表状态,确保上一首/下一首状态正确
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
} else {
|
|
|
@@ -2040,10 +2048,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 设置数据恢复完成标识
|
|
|
this.isDataRestored = true;
|
|
|
-
|
|
|
+
|
|
|
// 立即更新播放列表状态,确保上一首/下一首状态正确
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
-
|
|
|
+
|
|
|
LogUtils.getInstance().LOGI('UnifiedPlayerService: Data restoration completed');
|
|
|
} catch (error) {
|
|
|
console.log(`Heanup2 UnifiedPlayerService: 恢复持久化状态时出错: ${error}`);
|
|
|
@@ -2279,10 +2287,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
try {
|
|
|
// 确保播放列表状态是最新的
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
-
|
|
|
+
|
|
|
// 触发状态监听器通知
|
|
|
this.stateModel.forceNotifyStateChanged();
|
|
|
-
|
|
|
+
|
|
|
// 更新桌面卡片
|
|
|
await this.updateAllForms(true); // 强制更新
|
|
|
LogUtils.getInstance().LOGI('UnifiedPlayerService: Current state broadcasted manually');
|
|
|
@@ -2430,10 +2438,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
|
|
|
// 强制同步状态模型
|
|
|
this.stateModel.updatePlayingState(isActuallyPlaying);
|
|
|
-
|
|
|
+
|
|
|
// 确保播放列表状态是最新的
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
-
|
|
|
+
|
|
|
// 更新 AVSession 状态
|
|
|
if (isActuallyPlaying) {
|
|
|
let currentSong= this.getCurrentSong();
|
|
|
@@ -2443,7 +2451,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
}
|
|
|
this.updateSessionPlayState(true); // 强制更新,不使用防抖
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 延迟更新卡片,确保状态已同步
|
|
|
setTimeout(() => {
|
|
|
this.updateWidgetsForStateChange();
|
|
|
@@ -2655,7 +2663,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
*/
|
|
|
private async updateWidgetsForPlayStateChange(): Promise<void> {
|
|
|
try {
|
|
|
- this.updateAllForms();
|
|
|
+ this.updateAllForms();
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService: Widgets updated for play state change`);
|
|
|
} catch (error) {
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update widgets for play state change: ${error}`);
|
|
|
@@ -2667,24 +2675,27 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
* 将VideoItem和PlayerState数据传入桌面卡片,控制卡片的显示
|
|
|
*/
|
|
|
async updateAllForms(forceUpdate: boolean = false) {
|
|
|
+ // 定义变量在方法开始处,确保在整个方法中都可以访问
|
|
|
+ let imgMap: Record<string, number> = {};
|
|
|
+
|
|
|
try {
|
|
|
const now = Date.now();
|
|
|
-
|
|
|
+
|
|
|
// 防重复调用:如果正在更新或距离上次更新时间太近,则跳过(除非强制更新)
|
|
|
if (!forceUpdate && (this.isUpdatingForms || (now - this.lastUpdateTime) < this.UPDATE_THROTTLE_MS)) {
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Skipping duplicate call (last update: ${now - this.lastUpdateTime}ms ago)`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
this.isUpdatingForms = true;
|
|
|
this.lastUpdateTime = now;
|
|
|
-
|
|
|
+
|
|
|
// 确保播放列表状态是最新的
|
|
|
this.updatePlaylistStateInModelSync();
|
|
|
-
|
|
|
+
|
|
|
const formData: VideoItem | null = this.getCurrentSong(); // VideoItem
|
|
|
const songState: PlayerState = this.getCurrentState(); // PlayerState
|
|
|
-
|
|
|
+
|
|
|
if (!formData) {
|
|
|
LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No current song data');
|
|
|
return;
|
|
|
@@ -2717,18 +2728,84 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
hasPrevious: songState.hasPrevious || false,
|
|
|
playMode: songState.playMode || 0
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
const widgetPlaylistInfo: WidgetPlaylistInfo = {
|
|
|
currentIndex: this.getCurrentIndex(),
|
|
|
totalCount: this.getPlaylist().length
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
const widgetTimeInfo: WidgetTimeInfo = {
|
|
|
currentTimeText: this.formatTimeDisplay(Math.floor((songState.currentPosition || 0) / 1000)),
|
|
|
totalTimeText: this.formatTimeDisplay(Math.floor((songState.duration || 0) / 1000)),
|
|
|
progressPercentage: this.calculateProgressPercentage(songState.currentPosition || 0, songState.duration || 0)
|
|
|
};
|
|
|
|
|
|
+ // 处理封面图片
|
|
|
+ let imgName: string = '';
|
|
|
+
|
|
|
+ // 如果歌曲有封面图片路径,尝试处理图片
|
|
|
+ if (formData.pixelMapPath && formData.pixelMapPath.trim() !== '') {
|
|
|
+ try {
|
|
|
+ // 生成唯一的图片名称,确保每次更新时图片能正确刷新
|
|
|
+ imgName = 'songCover_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
+
|
|
|
+ // 判断是否为网络图片
|
|
|
+ if (formData.pixelMapPath.startsWith('http://') || formData.pixelMapPath.startsWith('https://')) {
|
|
|
+ // 处理网络图片:下载到临时文件
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Downloading network image ${formData.pixelMapPath}`);
|
|
|
+
|
|
|
+ const tempDir = this.context?.getApplicationContext().tempDir;
|
|
|
+ if (!tempDir) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: No temp directory available for network image`);
|
|
|
+ } else {
|
|
|
+ const tempFileName = imgName + '.tmp';
|
|
|
+ const tempFilePath = tempDir + '/' + tempFileName;
|
|
|
+
|
|
|
+ // 创建HTTP请求下载图片
|
|
|
+ const httpRequest = http.createHttp();
|
|
|
+ try {
|
|
|
+ const response = await httpRequest.request(formData.pixelMapPath, {
|
|
|
+ method: http.RequestMethod.GET,
|
|
|
+ connectTimeout: 5000, // 5秒超时,避免卡片更新被阻塞
|
|
|
+ readTimeout: 5000
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.responseCode === http.ResponseCode.OK && response.result) {
|
|
|
+ // 将下载的图片数据写入临时文件
|
|
|
+ const tempFile = fileIo.openSync(tempFilePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
|
|
|
+
|
|
|
+ try {
|
|
|
+ await fileIo.write(tempFile.fd, response.result as ArrayBuffer);
|
|
|
+ imgMap[imgName] = tempFile.fd;
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Downloaded and opened network image ${formData.pixelMapPath} to ${tempFilePath} with fd ${tempFile.fd}, imgName: ${imgName}`);
|
|
|
+ } catch (writeError) {
|
|
|
+ fileIo.closeSync(tempFile);
|
|
|
+ // throw writeError;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to download network image ${formData.pixelMapPath}: HTTP ${response.responseCode}`);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ httpRequest.destroy();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 处理本地图片文件
|
|
|
+ let file = fileIo.openSync(formData.pixelMapPath, fileIo.OpenMode.READ_ONLY);
|
|
|
+ imgMap[imgName] = file.fd;
|
|
|
+
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Opened local cover image ${formData.pixelMapPath} with fd ${file.fd}, imgName: ${imgName}`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to process cover image ${formData.pixelMapPath}: ${JSON.stringify(error as BusinessError)}`);
|
|
|
+ // 如果处理图片失败,使用空的imgMap
|
|
|
+ imgMap = {};
|
|
|
+ imgName = '';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: No cover image path available`);
|
|
|
+ }
|
|
|
|
|
|
// 平铺数据结构,确保桌面卡片能正确接收
|
|
|
const flatWidgetData: widgeData = {
|
|
|
@@ -2740,9 +2817,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
pixelMapPath: formData.pixelMapPath || '',
|
|
|
duration: typeof formData.duration === 'string' ? parseInt(formData.duration || '0') : (formData.duration || 0),
|
|
|
filePath: formData.filePath || '',
|
|
|
- imgName: '',
|
|
|
+ imgName: imgName, // 使用生成的唯一图片名称
|
|
|
imageColorHex: '2A2A2A',
|
|
|
-
|
|
|
+
|
|
|
// PlayerState数据平铺
|
|
|
isPlaying: widgetPlayerState.isPlaying,
|
|
|
isPaused: widgetPlayerState.isPaused,
|
|
|
@@ -2751,20 +2828,23 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
hasNext: widgetPlayerState.hasNext,
|
|
|
hasPrevious: widgetPlayerState.hasPrevious,
|
|
|
playMode: widgetPlayerState.playMode,
|
|
|
-
|
|
|
+
|
|
|
// 播放列表信息平铺
|
|
|
currentIndex: widgetPlaylistInfo.currentIndex,
|
|
|
totalCount: widgetPlaylistInfo.totalCount,
|
|
|
-
|
|
|
+
|
|
|
// 时间信息平铺
|
|
|
currentTimeText: widgetTimeInfo.currentTimeText,
|
|
|
totalTimeText: widgetTimeInfo.totalTimeText,
|
|
|
progressPercentage: widgetTimeInfo.progressPercentage,
|
|
|
-
|
|
|
+
|
|
|
// 保持嵌套结构兼容性
|
|
|
playerState: widgetPlayerState,
|
|
|
playlistInfo: widgetPlaylistInfo,
|
|
|
- timeInfo: widgetTimeInfo
|
|
|
+ timeInfo: widgetTimeInfo,
|
|
|
+
|
|
|
+ // 卡片图片显示必填字段
|
|
|
+ formImages: imgMap
|
|
|
};
|
|
|
|
|
|
// 记录更新统计
|
|
|
@@ -2782,10 +2862,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
} catch (error) {
|
|
|
failedCount++;
|
|
|
const errorStr: string = error?.toString() || '';
|
|
|
-
|
|
|
+
|
|
|
// 检查是否是Form ID不存在的错误,并且增加更严格的判断条件
|
|
|
- if ((errorStr.includes('does not exist') || errorStr.includes('不存在')) &&
|
|
|
- !errorStr.includes('timeout') && !errorStr.includes('busy')) {
|
|
|
+ if ((errorStr.includes('does not exist') || errorStr.includes('不存在')) &&
|
|
|
+ !errorStr.includes('timeout') && !errorStr.includes('busy')) {
|
|
|
// 只有明确是"不存在"错误且不是临时性错误时才标记为无效
|
|
|
invalidFormIds.push(formId);
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Form ${formId} does not exist, will be removed from storage`);
|
|
|
@@ -2806,9 +2886,42 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
}
|
|
|
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Updated ${successCount}/${formIds.length} forms with song "${formData.name}", playing: ${songState.isPlaying}, failed: ${failedCount}, cleaned: ${invalidFormIds.length}`);
|
|
|
+
|
|
|
} catch (error) {
|
|
|
LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms error: ${error}`);
|
|
|
} finally {
|
|
|
+ // 关闭图片文件描述符并清理临时文件
|
|
|
+ try {
|
|
|
+ const tempDir = this.context?.getApplicationContext().tempDir;
|
|
|
+
|
|
|
+ // 遍历imgMap中的所有条目
|
|
|
+ const imgKeys = Object.keys(imgMap);
|
|
|
+ for (let i = 0; i < imgKeys.length; i++) {
|
|
|
+ const imgKey = imgKeys[i];
|
|
|
+ const fd = imgMap[imgKey];
|
|
|
+ if (typeof fd === 'number') {
|
|
|
+ // 关闭文件描述符
|
|
|
+ fileIo.closeSync( fd);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Closed image file descriptor ${fd} for ${imgKey}`);
|
|
|
+
|
|
|
+ // 如果是网络图片的临时文件,尝试删除
|
|
|
+ if (tempDir && imgKey.startsWith('songCover_')) {
|
|
|
+ try {
|
|
|
+ const tempFilePath = tempDir + '/' + imgKey + '.tmp';
|
|
|
+ if (fileIo.accessSync(tempFilePath)) {
|
|
|
+ fileIo.unlinkSync(tempFilePath);
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Deleted temp file ${tempFilePath}`);
|
|
|
+ }
|
|
|
+ } catch (deleteError) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to delete temp file for ${imgKey}: ${deleteError}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to close image file descriptors: ${error}`);
|
|
|
+ }
|
|
|
+
|
|
|
// 重置更新标志
|
|
|
this.isUpdatingForms = false;
|
|
|
}
|
|
|
@@ -2836,7 +2949,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
*/
|
|
|
private async updateWidgetsForSongChange(song: VideoItem, forceUpdate: boolean = false): Promise<void> {
|
|
|
|
|
|
- this.updateAllForms();
|
|
|
+ this.updateAllForms();
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -3049,17 +3162,17 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
|
|
|
const playerDuration = ijkPlayer ? ijkPlayer.getDuration() : 0;
|
|
|
const shouldWaitForPlayer = playerDuration <= 0 && this.isPlayerPreparedForCurrentSong === false;
|
|
|
this.avMetadataUpdateTimer = setTimeout( () => {
|
|
|
- this.doUpdateAvSessionMetadata(song);
|
|
|
+ this.doUpdateAvSessionMetadata(song);
|
|
|
}, 800);
|
|
|
if (shouldWaitForPlayer) {
|
|
|
// 播放器还没准备好,延迟更新
|
|
|
this.avMetadataUpdateTimer = setTimeout( () => {
|
|
|
- this.doUpdateAvSessionMetadata(song);
|
|
|
+ this.doUpdateAvSessionMetadata(song);
|
|
|
}, 800);
|
|
|
} else {
|
|
|
// 播放器已准备好或者不需要等待,稍微延迟更新
|
|
|
this.avMetadataUpdateTimer = setTimeout( () => {
|
|
|
- this.doUpdateAvSessionMetadata(song);
|
|
|
+ this.doUpdateAvSessionMetadata(song);
|
|
|
}, 800);
|
|
|
}
|
|
|
}
|