| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891 |
- import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
- import { Want } from '@kit.AbilityKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { fileIo } from '@kit.CoreFileKit';
- import { http } from '@kit.NetworkKit';
- import { WidgetDataManager } from '../common/widget/WidgetDataManager';
- import { PlayerControlService } from '../common/widget/PlayerControlService';
- import { AvSessionWidgetListener } from '../common/widget/AvSessionWidgetListener';
- import { WidgetData, WidgetSize, FormattedWidgetData } from '../common/widget/WidgetTypes';
- import { FormLayoutManager } from '../common/widget/FormLayoutManager';
- import { GlobalWidgetManager } from '../common/widget/GlobalWidgetManager';
- import { WidgetSizeAdapter, SizeChangeListener } from '../common/widget/WidgetSizeAdapter';
- import { PreferencesUtil } from '../common/utils/PreferencesUtil';
- const TAG = 'Heanup EntryFormAbility';
- /**
- * 扩展的卡片数据接口,支持图片传递
- */
- interface ExtendedWidgetData extends FormattedWidgetData {
- formImages?: Record<string, number>;
- }
- /**
- * 自定义对象复制函数,替代Object.assign
- */
- function copyWidgetData(target: ExtendedWidgetData, overrides: Partial<ExtendedWidgetData>): ExtendedWidgetData {
- return {
- isPlaying: overrides.isPlaying !== undefined ? overrides.isPlaying : target.isPlaying,
- isPaused: overrides.isPaused !== undefined ? overrides.isPaused : target.isPaused,
- isLoading: overrides.isLoading !== undefined ? overrides.isLoading : target.isLoading,
- songTitle: overrides.songTitle !== undefined ? overrides.songTitle : target.songTitle,
- songArtist: overrides.songArtist !== undefined ? overrides.songArtist : target.songArtist,
- songAlbum: overrides.songAlbum !== undefined ? overrides.songAlbum : target.songAlbum,
- coverImage: overrides.coverImage !== undefined ? overrides.coverImage : target.coverImage,
- currentTime: overrides.currentTime !== undefined ? overrides.currentTime : target.currentTime,
- totalTime: overrides.totalTime !== undefined ? overrides.totalTime : target.totalTime,
- progressPercentage: overrides.progressPercentage !== undefined ? overrides.progressPercentage :
- target.progressPercentage,
- hasNext: overrides.hasNext !== undefined ? overrides.hasNext : target.hasNext,
- hasPrevious: overrides.hasPrevious !== undefined ? overrides.hasPrevious : target.hasPrevious,
- showProgress: overrides.showProgress !== undefined ? overrides.showProgress : target.showProgress,
- showCover: overrides.showCover !== undefined ? overrides.showCover : target.showCover,
- widgetSize: overrides.widgetSize !== undefined ? overrides.widgetSize : target.widgetSize,
- timestamp: overrides.timestamp !== undefined ? overrides.timestamp : target.timestamp,
- imgName: overrides.imgName !== undefined ? overrides.imgName : target.imgName,
- formImages: overrides.formImages !== undefined ? overrides.formImages : target.formImages
- };
- }
- /**
- * 桌面播放器卡片扩展能力
- * 负责处理卡片的生命周期管理和用户交互事件
- * 支持多尺寸适配和UI重构
- */
- export default class EntryFormAbility extends FormExtensionAbility
- implements SizeChangeListener {
- private widgetDataManager: WidgetDataManager | null = null;
- private playerControlService: PlayerControlService = new PlayerControlService();
- private layoutManager: FormLayoutManager = FormLayoutManager.getInstance();
- private sizeAdapter: WidgetSizeAdapter = WidgetSizeAdapter.getInstance();
- private globalWidgetManager: GlobalWidgetManager = GlobalWidgetManager.getInstance();
- // 使用实例变量而不是静态变量,确保每个进程都能正确设置监听器
- private globalListenerSetup: boolean = false;
- // 防抖机制:避免短时间内重复更新
- private lastUpdateTime: number = 0;
- private updateDebounceDelay: number = 100;
- // 100ms防抖延迟
- // 进程启动时间,用于检测进程重启
- private processStartTime: number = Date.now();
- // 图片下载缓存
- private downloadingImages: Map<string, Promise<string | null>> = new Map();
- private imageCache: Map<string, string> = new Map();
- // url -> fileName映射
- /**
- * 卡片创建时调用
- */
- onAddForm(want: Want): formBindingData.FormBindingData {
- // 检查参数有效性
- if (!want || !want.parameters) {
- hilog.error(0x0000, TAG, 'FormAbility onAddForm want or want.parameters is undefined');
- return formBindingData.createFormBindingData('');
- }
- // 初始化服务
- try {
- this.initializeServices();
- } catch (error) {
- hilog.error(0x0000, TAG, `Heanup EntryFormAbility failed to initialize services: ${error}`);
- }
- const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string;
- const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string;
- const formDimension = want.parameters?.['ohos.extra.param.key.form_dimension'] as string;
- const tempFlag = want.parameters?.['ohos.extra.param.key.form_temporary'] as boolean;
- // 持久化保存 Form ID(异步执行,不阻塞返回)
- this.saveFormIdToPersistence(formId).then(() => {
- })
- // 检测卡片尺寸并注册到全局管理器
- const widgetSize = this.sizeAdapter.detectSizeFromWant(want);
- try {
- this.globalWidgetManager.registerWidget(formId, widgetSize);
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to register widget: ${error}`);
- }
- // 注册各种监听器
- this.sizeAdapter.registerSizeChangeListener(formId, this);
- // 初始化卡片数据
- this.initializeWidget(formId, widgetSize);
- // 验证监听器设置状态
- setTimeout(() => {
- const avSessionListener = AvSessionWidgetListener.getInstance();
- const listenerCount = avSessionListener.getListenerCount();
- if (listenerCount === 0) {
- hilog.warn(0x0000, TAG, `⚠️ No listeners found! Form process may not receive data updates!`);
- } else {
- }
- }, 2000);
- // 获取当前播放状态而不是初始数据
- this.playerControlService.getCurrentPlayState().then((currentState: WidgetData) => {
- const adaptedData = this.layoutManager.adaptDataForSize(currentState, widgetSize);
- const formData = formBindingData.createFormBindingData(adaptedData);
- // 立即更新卡片以显示当前状态
- formProvider.updateForm(formId, formData);
- });
- // 返回初始数据作为临时显示
- const initialData = this.widgetDataManager?.getInitialWidgetData() || this.getDefaultWidgetData();
- const adaptedData = this.layoutManager.adaptDataForSize(initialData, widgetSize);
- return formBindingData.createFormBindingData(adaptedData);
- }
- /**
- * 卡片更新时调用
- */
- onUpdateForm(formId: string): void {
- // 确保服务已初始化
- if (!this.globalListenerSetup) {
- this.initializeServices();
- }
- // 确保widget已注册到GlobalWidgetManager
- if (!this.globalWidgetManager.hasWidget(formId)) {
- this.globalWidgetManager.registerWidget(formId, 'medium' as WidgetSize);
- }
- // 只更新指定的卡片,避免重复更新
- this.updateWidgetData(formId);
- }
- /**
- * 卡片删除时调用
- */
- onRemoveForm(formId: string): void {
- // 从持久化存储中移除 Form ID(异步执行)
- this.removeFormIdFromPersistence(formId).then(() => {
- });
- // 注销各种监听器
- this.sizeAdapter.unregisterSizeChangeListener(formId);
- // 从全局管理器中注销卡片
- this.globalWidgetManager.unregisterWidget(formId);
- // 清理卡片相关数据和配置
- this.widgetDataManager?.removeWidgetData(formId);
- }
- /**
- * 卡片可见性变化时调用
- */
- onVisibilityChange(newStatus: Record<string, number>): void {
- const formIds = Object.keys(newStatus);
- for (let i = 0; i < formIds.length; i++) {
- const formId = formIds[i];
- const isVisible = newStatus[formId] === 1;
- if (isVisible) {
- // 卡片变为可见时,更新数据
- this.updateWidgetData(formId);
- }
- }
- }
- /**
- * 卡片配置更新时调用
- */
- onConfigurationUpdate(newConfig: Object): void {
- // 更新所有卡片以适应新配置
- this.widgetDataManager?.updateAllWidgets();
- }
- /**
- * 实现SizeChangeListener接口
- * 处理卡片尺寸变化事件
- */
- onSizeChanged(formId: string, oldSize: WidgetSize, newSize: WidgetSize): void {
- // 更新全局管理器中的尺寸记录
- this.globalWidgetManager.updateWidgetSize(formId, newSize);
- // 立即更新卡片数据以适应新尺寸
- this.updateWidgetData(formId);
- }
- /**
- * 处理卡片尺寸变化(系统调用)
- * @param newStatus 新的尺寸状态
- */
- onAcquireFormState(want: Want): number {
- const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string;
- if (formId) {
- // 检测新的尺寸
- const newSize = this.sizeAdapter.detectSizeFromWant(want);
- const oldSize = this.globalWidgetManager.getWidgetSize(formId);
- if (oldSize && this.sizeAdapter.validateSizeChange(oldSize, newSize)) {
- // 处理尺寸变化
- this.handleFormSizeChange(formId, oldSize, newSize);
- }
- }
- // 返回卡片状态 - 使用数字常量代替枚举
- return 1; // READY状态
- }
- /**
- * 初始化服务
- */
- private initializeServices(): void {
- if (!this.widgetDataManager) {
- this.widgetDataManager = new WidgetDataManager();
- }
- // 设置全局状态监听器(每个进程实例设置一次)
- if (!this.globalListenerSetup) {
- this.setupGlobalStateListener();
- this.globalListenerSetup = true;
- }
- }
- /**
- * 设置全局状态监听器
- */
- private setupGlobalStateListener(): void {
- try {
- const avSessionListener = AvSessionWidgetListener.getInstance();
- avSessionListener.addStateListener((data: WidgetData) => {
- this.updateAllWidgetsWithData(data);
- });
- } catch (error) {
- hilog.error(0x0000, TAG,
- `Heanup EntryFormAbility [Process:${this.processStartTime}] failed to setup global listener: ${error}`);
- }
- }
- /**
- * 使用指定数据更新所有卡片
- */
- private updateAllWidgetsWithData(data: WidgetData): void {
- // 防抖机制:避免短时间内重复更新
- const now = Date.now();
- if (now - this.lastUpdateTime < this.updateDebounceDelay) {
- return;
- }
- this.lastUpdateTime = now;
- const activeWidgets = this.globalWidgetManager.getActiveWidgets();
- if (activeWidgets.size === 0) {
- hilog.warn(0x0000, TAG, 'Heanup EntryFormAbility no widgets to update in this process, skipping');
- return;
- }
- activeWidgets.forEach((size: WidgetSize, formId: string) => {
- this.updateSingleWidget(formId, size, data);
- });
- }
- /**
- * 更新单个卡片,带重试机制和网络图片支持
- */
- private updateSingleWidget(formId: string, size: WidgetSize, data: WidgetData, retryCount: number = 0): void {
- try {
- // 根据卡片尺寸适配数据
- const formattedData = this.layoutManager.adaptDataForSize(data, size);
- // 转换为ExtendedWidgetData
- const adaptedData: ExtendedWidgetData = {
- isPlaying: formattedData.isPlaying,
- isPaused: formattedData.isPaused,
- isLoading: formattedData.isLoading,
- songTitle: formattedData.songTitle,
- songArtist: formattedData.songArtist,
- songAlbum: formattedData.songAlbum,
- coverImage: formattedData.coverImage,
- currentTime: formattedData.currentTime,
- totalTime: formattedData.totalTime,
- progressPercentage: formattedData.progressPercentage,
- hasNext: formattedData.hasNext,
- hasPrevious: formattedData.hasPrevious,
- showProgress: formattedData.showProgress,
- showCover: formattedData.showCover,
- widgetSize: formattedData.widgetSize,
- timestamp: formattedData.timestamp,
- imgName: formattedData.imgName,
- formImages: formattedData.formImages
- };
- // 使用统一的图片处理方法
- this.updateWidgetWithImage(formId, adaptedData, retryCount);
- } catch (error) {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} process error: ${error}`);
- // 如果是处理错误,也尝试重试
- if (retryCount < 2) {
- setTimeout(() => {
- this.updateSingleWidget(formId, size, data, retryCount + 1);
- }, 1000 * (retryCount + 1));
- }
- }
- }
- /**
- * 直接更新卡片(无网络图片)
- */
- private updateWidgetDirectly(formId: string, adaptedData: ExtendedWidgetData, retryCount: number = 0): void {
- const formData = formBindingData.createFormBindingData(adaptedData);
- formProvider.updateForm(formId, formData).then(() => {
- }).catch(() => {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} update failed, retry count: ${retryCount}`);
- // 重试机制:最多重试2次
- if (retryCount < 2) {
- setTimeout(() => {
- this.updateWidgetDirectly(formId, adaptedData, retryCount + 1);
- }, 1000 * (retryCount + 1)); // 递增延迟
- }
- });
- }
- /**
- * 统一处理图片更新
- */
- private async updateWidgetWithImage(formId: string, adaptedData: ExtendedWidgetData,
- retryCount: number = 0): Promise<void> {
- try {
- if (!adaptedData.coverImage || adaptedData.coverImage.trim() === '') {
- // 没有封面图片,直接更新
- this.updateWidgetDirectly(formId, adaptedData, retryCount);
- return;
- }
- if (this.isNetworkUrl(adaptedData.coverImage)) {
- // 处理网络图片
- await this.updateWidgetWithNetworkImage(formId, adaptedData, retryCount);
- } else if (this.isLocalFileUri(adaptedData.coverImage)) {
- // 处理本地文件URI
- await this.updateWidgetWithLocalImage(formId, adaptedData, retryCount);
- } else {
- // 其他情况,可能是相对路径或其他格式,直接使用
- this.updateWidgetDirectly(formId, adaptedData, retryCount);
- }
- } catch (error) {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} image update error: ${error}`);
- // 失败时使用默认数据
- const dataWithoutImage: ExtendedWidgetData = copyWidgetData(adaptedData, { coverImage: '' });
- this.updateWidgetDirectly(formId, dataWithoutImage, retryCount);
- }
- }
- /**
- * 处理本地文件URI图片
- */
- private async updateWidgetWithLocalImage(formId: string, adaptedData: ExtendedWidgetData,
- retryCount: number = 0): Promise<void> {
- try {
- const localFileUri: string = adaptedData.coverImage;
- // 先用无图片的数据快速更新一次,确保界面响应性
- const dataWithoutImage: ExtendedWidgetData = copyWidgetData(adaptedData, {
- coverImage: '',
- imgName: '',
- formImages: undefined
- });
- this.updateWidgetDirectly(formId, dataWithoutImage, 0);
- // 处理本地图片文件
- const fileName = await this.processLocalImageFile(localFileUri);
- if (fileName) {
- try {
- // 按照官方文档要求,准备 formImages 和文件描述符
- const imageMap: Record<string, number> = {};
- const fileDescriptor = await this.getImageFileDescriptor(fileName);
- imageMap[fileName] = fileDescriptor;
- // 按照官方文档要求,imgName 必须与 formImages 中的 key 相同
- const dataWithImage: ExtendedWidgetData = copyWidgetData(adaptedData, {
- coverImage: '', // 清空原路径
- imgName: fileName, // 设置图片名称用于 memory:// 协议
- formImages: imageMap // 必填字段,不可缺省
- });
- const formDataWithImage = formBindingData.createFormBindingData(dataWithImage);
- await formProvider.updateForm(formId, formDataWithImage);
- } catch (fdError) {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} failed to get file descriptor: ${fdError}`);
- // 文件描述符获取失败,使用默认图片
- this.updateWidgetDirectly(formId, dataWithoutImage, 0);
- }
- } else {
- hilog.warn(0x0000, TAG, `Heanup widget ${formId} failed to process local image, using default`);
- // 图片处理失败,使用默认图片
- this.updateWidgetDirectly(formId, dataWithoutImage, 0);
- }
- } catch (error) {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} local image update error: ${error}`);
- // 失败后回退到无图片模式
- const dataWithoutImage: ExtendedWidgetData = copyWidgetData(adaptedData, {
- coverImage: '',
- imgName: '',
- formImages: undefined
- });
- this.updateWidgetDirectly(formId, dataWithoutImage, retryCount);
- }
- }
- /**
- * 使用网络图片更新卡片
- */
- private async updateWidgetWithNetworkImage(formId: string, adaptedData: ExtendedWidgetData,
- retryCount: number = 0): Promise<void> {
- try {
- const imageUrl: string = adaptedData.coverImage;
- // 先用无图片的数据快速更新一次
- const dataWithoutImage: ExtendedWidgetData = copyWidgetData(adaptedData, { coverImage: '' });
- this.updateWidgetDirectly(formId, dataWithoutImage, 0);
- // 下载图片
- const fileName = await this.downloadNetworkImage(imageUrl);
- if (fileName) {
- // 准备包含图片的数据
- const imageMap: Record<string, number> = {};
- imageMap[fileName] = await this.getImageFileDescriptor(fileName);
- const dataWithImage: ExtendedWidgetData = copyWidgetData(adaptedData, {
- coverImage: '', // 清空URL
- imgName: fileName, // 设置图片名称用于memory://协议
- formImages: imageMap
- });
- const formDataWithImage = formBindingData.createFormBindingData(dataWithImage);
- await formProvider.updateForm(formId, formDataWithImage);
- } else {
- hilog.warn(0x0000, TAG, `Heanup widget ${formId} failed to download image, using default`);
- // 图片下载失败,使用默认图片
- this.updateWidgetDirectly(formId, dataWithoutImage, 0);
- }
- } catch (error) {
- hilog.error(0x0000, TAG, `Heanup widget ${formId} network image update error: ${error}`);
- // 失败后回退到无图片模式
- const dataWithoutImage: ExtendedWidgetData = copyWidgetData(adaptedData, { coverImage: '' });
- this.updateWidgetDirectly(formId, dataWithoutImage, retryCount);
- }
- }
- /**
- * 检查是否为网络URL
- */
- private isNetworkUrl(url: string): boolean {
- return url.startsWith('http://') || url.startsWith('https://');
- }
- /**
- * 检查是否为本地文件URI
- */
- private isLocalFileUri(url: string): boolean {
- return url.startsWith('file://');
- }
- /**
- * 处理本地图片文件,将其复制到卡片可访问的临时目录
- */
- private async processLocalImageFile(fileUri: string): Promise<string | null> {
- try {
- // 检查缓存
- if (this.imageCache.has(fileUri)) {
- const fileName = this.imageCache.get(fileUri)!;
- // 验证缓存文件是否仍然存在 - 使用 FormExtensionAbility 的 tempDir
- const formTempDir = this.context.getApplicationContext().tempDir;
- const tempFilePath = `${formTempDir}/${fileName}`;
- if (fileIo.accessSync(tempFilePath)) {
- return fileName;
- } else {
- // 缓存文件已不存在,清除缓存记录
- this.imageCache.delete(fileUri);
- hilog.warn(0x0000, TAG, `Cached file no longer exists, will reprocess: ${fileName}`);
- }
- }
- // 检查是否正在处理
- if (this.downloadingImages.has(fileUri)) {
- const existingPromise = this.downloadingImages.get(fileUri);
- if (existingPromise) {
- return await existingPromise;
- }
- }
- // 开始处理本地文件
- const processPromise = this.performLocalImageCopy(fileUri);
- this.downloadingImages.set(fileUri, processPromise);
- const fileName = await processPromise;
- // 清理处理状态
- this.downloadingImages.delete(fileUri);
- if (fileName) {
- this.imageCache.set(fileUri, fileName);
- }
- return fileName;
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to process local image ${fileUri}: ${error}`);
- this.downloadingImages.delete(fileUri);
- return null;
- }
- }
- /**
- * 执行本地图片文件复制
- */
- private async performLocalImageCopy(fileUri: string): Promise<string | null> {
- try {
- // 转换 file:// URI 为实际文件路径
- const realPath = fileUri.replace('file://', '');
- // 检查源文件是否存在
- if (!fileIo.accessSync(realPath)) {
- hilog.error(0x0000, TAG, `Source image file does not exist: ${realPath}`);
- return null;
- }
- // 生成目标文件名(包含时间戳和随机数,确保每次都不同)
- const fileExtension = this.getFileExtension(realPath) || 'jpg';
- const fileName = `widget_local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.${fileExtension}`;
- // 使用 FormExtensionAbility 的 tempDir(官方文档要求)
- const formTempDir = this.context.getApplicationContext().tempDir;
- const tempFilePath = `${formTempDir}/${fileName}`;
- // 复制文件到临时目录
- fileIo.copyFileSync(realPath, tempFilePath);
- return fileName;
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to copy local image: ${error}`);
- return null;
- }
- }
- /**
- * 获取文件扩展名
- */
- private getFileExtension(filePath: string): string | null {
- const lastDotIndex = filePath.lastIndexOf('.');
- if (lastDotIndex === -1 || lastDotIndex === filePath.length - 1) {
- return null;
- }
- return filePath.substring(lastDotIndex + 1).toLowerCase();
- }
- /**
- * 下载网络图片
- */
- private async downloadNetworkImage(imageUrl: string): Promise<string | null> {
- try {
- // 检查缓存
- if (this.imageCache.has(imageUrl)) {
- const fileName = this.imageCache.get(imageUrl)!;
- return fileName;
- }
- // 检查是否正在下载
- if (this.downloadingImages.has(imageUrl)) {
- const existingPromise = this.downloadingImages.get(imageUrl);
- if (existingPromise) {
- return await existingPromise;
- }
- }
- // 开始下载
- const downloadPromise = this.performImageDownload(imageUrl);
- this.downloadingImages.set(imageUrl, downloadPromise);
- const fileName = await downloadPromise;
- // 清理下载状态
- this.downloadingImages.delete(imageUrl);
- if (fileName) {
- this.imageCache.set(imageUrl, fileName);
- }
- return fileName;
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to download image ${imageUrl}: ${error}`);
- this.downloadingImages.delete(imageUrl);
- return null;
- }
- }
- /**
- * 执行图片下载
- */
- private async performImageDownload(imageUrl: string): Promise<string | null> {
- try {
- // 创建HTTP请求
- const httpRequest = http.createHttp();
- // 设置超时时间为3秒,适合卡片的快速响应需求
- const response = await httpRequest.request(imageUrl, {
- method: http.RequestMethod.GET,
- expectDataType: http.HttpDataType.ARRAY_BUFFER,
- connectTimeout: 3000,
- readTimeout: 3000
- });
- if (response.responseCode === http.ResponseCode.OK && response.result) {
- // 生成文件名(确保每次都不同,符合官方文档要求)
- const fileName = `widget_cover_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
- // 使用 FormExtensionAbility 的 tempDir(官方文档要求)
- const formTempDir = this.context.getApplicationContext().tempDir;
- const filePath = `${formTempDir}/${fileName}`;
- // 保存文件
- const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
- await fileIo.write(file.fd, response.result as ArrayBuffer);
- fileIo.closeSync(file);
- httpRequest.destroy();
- return fileName;
- } else {
- hilog.error(0x0000, TAG, `HTTP request failed: ${response.responseCode}`);
- httpRequest.destroy();
- return null;
- }
- } catch (error) {
- hilog.error(0x0000, TAG, `Download error: ${error}`);
- return null;
- }
- }
- /**
- * 获取图片文件描述符
- */
- private async getImageFileDescriptor(fileName: string): Promise<number> {
- try {
- // 使用 FormExtensionAbility 的 tempDir(官方文档要求)
- const formTempDir = this.context.getApplicationContext().tempDir;
- const filePath = `${formTempDir}/${fileName}`;
- const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
- // 注意:文件描述符会被系统自动管理,不需要手动关闭
- // 系统会在卡片更新完成后自动关闭文件描述符
- return file.fd;
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to get file descriptor for ${fileName}: ${error}`);
- throw new Error(`Failed to get file descriptor for ${fileName}: ${error}`);
- }
- }
- /**
- * 获取默认卡片数据
- */
- private getDefaultWidgetData(): WidgetData {
- return {
- playState: {
- isPlaying: false,
- isPaused: true,
- isLoading: false
- },
- currentSong: {
- id: '',
- title: '暂无播放',
- artist: '未知艺术家',
- album: '未知专辑',
- coverImagePath: '',
- duration: 0
- },
- progress: {
- currentPosition: 0,
- duration: 0,
- percentage: 0,
- currentTimeText: '00:00',
- totalTimeText: '00:00'
- },
- playlist: {
- hasNext: false,
- hasPrevious: false,
- currentIndex: 0,
- totalCount: 0
- },
- config: {
- size: 'medium' as WidgetSize,
- theme: 'auto' as string,
- showProgress: true,
- showCover: true
- }
- };
- }
- /**
- * 初始化卡片
- */
- private async initializeWidget(formId: string, widgetSize: WidgetSize): Promise<void> {
- try {
- // 获取当前播放状态
- const currentState = await this.playerControlService.getCurrentPlayState();
- // 适配数据到指定尺寸
- const adaptedData = this.layoutManager.adaptDataForSize(currentState, widgetSize);
- await this.widgetDataManager?.saveWidgetData(formId, adaptedData);
- // 立即更新一次卡片数据
- await this.updateWidgetData(formId);
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to initialize widget ${formId}: ${error}`);
- }
- }
- /**
- * 更新卡片数据(简化版本)
- */
- private async updateWidgetData(formId: string): Promise<void> {
- try {
- const currentState = await this.playerControlService.getCurrentPlayState();
- // 获取卡片当前尺寸
- const currentSize = this.globalWidgetManager.getWidgetSize(formId) || WidgetSize.MEDIUM;
- // 适配数据到当前尺寸并直接更新
- const adaptedData = this.layoutManager.adaptDataForSize(currentState, currentSize);
- const formData = formBindingData.createFormBindingData(adaptedData);
- await formProvider.updateForm(formId, formData);
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to update widget ${formId}: ${error}`);
- }
- }
- /**
- * 处理表单尺寸变化
- */
- private async handleFormSizeChange(formId: string, oldSize: WidgetSize, newSize: WidgetSize): Promise<void> {
- try {
- // 获取当前播放状态
- const currentState = await this.playerControlService.getCurrentPlayState();
- // 使用适配器处理尺寸变化
- const adaptedData = this.sizeAdapter.handleSizeChange(formId, oldSize, newSize, currentState);
- // 更新卡片数据
- await this.widgetDataManager?.updateWidget(formId, adaptedData);
- } catch (error) {
- hilog.error(0x0000, TAG, `Failed to handle form size change: ${error}`);
- }
- }
- /**
- * 保存 Form ID 到持久化存储
- */
- private async saveFormIdToPersistence(formId: string): Promise<void> {
- try {
- const preferencesUtil = PreferencesUtil.getInstance();
- const prefs = await preferencesUtil.getPreferences(this.context);
- await preferencesUtil.addFormId(prefs, formId);
- } catch (error) {
- hilog.error(0x0000, TAG, `❌ Failed to save Form ID: ${error}`);
- }
- }
- /**
- * 从持久化存储中移除 Form ID
- */
- private async removeFormIdFromPersistence(formId: string): Promise<void> {
- try {
- const preferencesUtil = PreferencesUtil.getInstance();
- const prefs = await preferencesUtil.getPreferences(this.context);
- await preferencesUtil.removeFormId(prefs, formId);
- } catch (error) {
- hilog.error(0x0000, TAG, `❌ Failed to remove Form ID: ${error}`);
- }
- }
- }
|