import TitleBar from './TitleBar' import { curves, display, PiPWindow, promptAction, router, SymbolGlyphModifier, window } from '@kit.ArkUI'; import { VideoItem } from '../viewmodel/VideoItem'; import { Playlist } from '../viewmodel/Playlist'; import { LengthMetrics, SegmentButton,borderRadiuses, SegmentButtonOptions } from '@kit.ArkUI'; import { AppUtil, ArrayUtil, Base64Util, DateUtil, DeviceUtil, DisplayUtil, FileUtil, ImageUtil, LogUtil, MD5, PreferencesUtil, RandomUtil, StrUtil, ToastUtil } from '@pura/harmony-utils'; import { imagePathToPixelMap } from '../common/util/CommUtils'; import { unifiedDataChannel, uniformDataStruct, uniformTypeDescriptor } from '@kit.ArkData'; import { CommonConstants } from '../common/constants/CommonConstants'; import { EventConstants } from '../common/constants/EventConstants'; import Logger from '../common/util/Logger'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { BusinessError, emitter } from '@kit.BasicServicesKit'; import { BubbleBean } from '../viewmodel/BubbleBean'; import { PopupPosition, XPopup } from '@chinalike/popup'; import { common, ConfigurationConstant } from '@kit.AbilityKit'; import { AnimationHelper, DialogAction, DialogHelper } from '@pura/harmony-dialog'; import { fileIo, fileUri, picker } from '@kit.CoreFileKit'; import { MessageEvents, util, worker, ErrorEvent } from '@kit.ArkTS'; import { Verify } from './Verify'; import { taskpool } from '@kit.ArkTS'; import { RotatingCover } from './RotatingCover'; import { PlayConstants } from '../common/constants/PlayConstants'; import { effectKit } from '@kit.ArkGraphics2D'; import { ColorConversion } from '../common/util/ColorConversion'; import { LyricController, LyricParser, LyricView2 } from '@seagazer/cclyric'; import { AvSessionController } from '../controller/AvSessionController'; import { repairAudioMetadata, convertDsfToWav, getApiLyric,changeMusicCover} from '../common/util/MusicTagUtils'; import { FFMpegTags,Utility } from '../common/util/Utility'; import { TagsContentCover } from '../view/TagsContentCover'; import { ImageFancyModifier } from '../common/util/AttributeModifierUtil' import { // DeviceChangeReason, IjkMediaPlayer, InterruptEvent, InterruptHintType, LogUtils, OnBufferingUpdateListener, OnCompletionListener, OnErrorListener, OnInfoListener, OnPreparedListener, OnSeekCompleteListener, OnTimedTextListener, OnVideoSizeChangedListener } from '@ohos/ijkplayer'; import { PlayStatus } from '../common/PlayStatus'; import fs from '@ohos.file.fs'; import { image } from '@kit.ImageKit'; import { AVCastPicker, AVCastPickerState, AVCastPickerStyle, avSession } from '@kit.AVSessionKit'; import { UniversalDetector } from '@ohos/juniversalchardet'; import { SettingPage } from '../pages/SettingPage'; import { secondToTime,getTransverterText } from '../common/util/CommUtils'; import { CommonConstants2 } from '../common/util/CommonConstants2'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { LazyDataSource } from '../common/util/LazyDataSource'; import MediaTable from '../common/util/MediaTable'; import NetAxiosUtil from '../common/util/NetAxiosUtil'; import ImageUtils from '../common/util/ImageUtils'; import { ringtone } from '@kit.RingtoneKit'; import { BreakpointType, BreakpointTypeEnum } from '../common/util/BreakpointSystem'; import { resourceManager } from '@kit.LocalizationKit'; import { deviceInfo } from '@kit.BasicServicesKit'; import { HSBColorPicker, HSBColorPickerLayout } from '@keke/color-picker' import app from '@system.app'; import { TextNodeController } from './PipLyricTextBuilder'; import { IndexerView } from './IndexerView'; import { KeyCode } from '@kit.InputKit'; import { KnockController } from '../controller/KnockController'; import { DeleteComptent } from '../view/DeleteComptent'; import { ABLoopComptent } from '../view/ABLoopComptent'; import { FixMessyView } from '../view/FixMessyView'; import { parseCueFile,CueTrack,CueInfo } from '../common/util/CueUtils'; import { CueComptent } from '../view/CueComptent'; import PlaylistTable from '../common/util/PlaylistTable'; import { showAddToPlaylistDialog } from '../dialog/AddToPlaylistDialog'; import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog'; const TAG = 'LocalMusic'; /** * 歌单播放事件数据 */ interface PlaylistEventData { playlistId: string; playlistName: string; songCount: number; startIndex: number; songFilePaths: string[]; } const DEFAULT_INDEX = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] //排序类型 function getTypeOrder(type: number) { switch (type) { case CommonConstants.TYPE_IS_DIR: return 1; // First case CommonConstants.TYPE_IS_CSJAD: return 2; // Middle case CommonConstants.TYPE_LOCAL: return 3; // Last default: return 4; // Unknown types, if any, go last } } const workerInstance = new worker.ThreadWorker("entry/ets/workers/Worker.ets"); // 定义接口 interface HiCarAspectRatio { ratio: number; name: string; } interface GeneratedObjectLiteralInterface_1 { assetId: string; title: string; artist: string; // 网络资源投播,设置mediaUri; 本地资源投播,将本地文件打开后,相关的文件描述符设置到fdSrc mediaUri: string; // 该字段大写,音频'AUDIO',视频'VIDEO' mediaType: string; mediaSize: number; //startPosition为投播当前进度,设置该字段可将本机播放进度同步到远端 startPosition: number; // 投播资源播放时长,设置该字段可将本机播放时长同步到远端显示 duration: number; albumCoverUri: string; albumTitle: string; appName: string; // DRM资源,需要配置支持的DRM类型, 以chinaDRM为例。 drmScheme: string; } const ITEM_HEIGHT_SMALL: number = 48; // 列表项小高度 const ITEM_HEIGHT: number = 58; // 列表项中高度 const ITEM_HEIGHT_BIG: number = 78; // 列表项大高度 //功能强大的音乐播放器 @Preview @Component export struct LocalMusic { @Consume currentSongList: Array//当前歌单 @Consume currentSongListName:string //当前歌单名称 @Consume @Watch('onIDChange') currentSongListID:string //当前歌单ID @State mediaKuCount: number = 0; @State albumCount: number = 0; @State artistCount: number = 0; @State fastForwardSeconds: string = '10' @State isShowBackFast: boolean = true//快进快退按钮 @State isCanAuto:boolean = true @State opacityValueImage: number = 1; @State tipPopup:boolean = false @Consume mType: number; @StorageProp('themeColor') themeColor: string = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR); themeMode: number = 0; @State titleName: string = '首页' @State textStr: string = '' // static readonly STR_MUSIC_VIDEO: string = 'Audios'; // private download_path: string = ''; @Consume rootPath: string //音频根目录 @Consume currentPath: string static readonly STR_LOCK_VIDEO: string = '.私密音频'; static readonly STR_FAC_VIDEO: string = '.我的收藏'; @State lockPath: string = '' @State favPath: string = '' static readonly STR_HISTORY_MUSIC: string = '.最近播放'; @State historyPath: string = '' @State historyList: Array = [] @Consume isHistory: boolean static readonly HISTORY_MUSIC: string = 'music_historyList'; private table: MediaTable = new MediaTable(getContext(this)) private playlistTable: PlaylistTable | null = null @State allPlaylists: Playlist[] = [] @State isZero: boolean = false @State fileList: Array = [] @State dirList: Array = [] @Consume videoLocalList: Array @State dataSource: LazyDataSource = new LazyDataSource(this.videoLocalList) @State mediaKuList: Array = []; //媒体库文件 @State artistList: Array = []; //艺术家文件 @State artistMap: Map = new Map(); //艺术家Map @State albumList: Array = []; //专辑文件 @State albumMap: Map = new Map(); //专辑Map private mScrollMap: Map = new Map(); // 艺术家、专辑名->滚动index @State imageOpacities: number[] = []; @Consume isCanBack: boolean @Consume @Watch('onModeChange') modeType: number; // 0首页,1媒体库,2艺术家,3专辑 @State showSingleLyric: boolean = false @State sortType: number = 4 //默认排序方式 @State isShowSimi: boolean = false //是否显示私密音频 @State isShowFAV: boolean = true //是否显示我的收藏 @State isShowTitleBar: boolean = true //是否显示分类导航条 @State isShowAllBar: boolean = true //是否显示播放全部条 @State isShowSingleLineLyric: boolean = false //是否显示单行歌词 @State isShowHistory: boolean = true //是否显示最近播放 @State @Watch('onColorModeChange') isCustomizeBg: boolean = false //自定义背景界面 @State isGridMusic: boolean = false //是否网格布局 @State isCircleBtn: boolean = false //是否圆形播放按钮 @State twoFingerType: number = 3 //双支放大缩小的类型 @State isScrollHide: boolean = false //是否滚动隐藏 @State isSameTimePlay: boolean = false //是否和其他app同时播放 @State isStartAutoPlay: boolean = false //启动后自动播放 @State isShowPlayPageBack: boolean = false //是否显示播放页返回键 @State isShowHeader: boolean = true @State isPlayListBgGrass: boolean = true //是否播放列表玻璃透明效果 @State isMemoryLastPlay: boolean = false //是否启用应用退出记忆最后一首的播放进度 @State isCoverTopBig: boolean = false //顶部大封面部分手机显示会和播放控制页重叠 @State isSwipe: boolean = false //listItem的左滑开关 @State openSkipSongAnimate: boolean = true//切歌动画效果 @State customizeBgPath: string | undefined = ''; @State isDarkMode: boolean = false @State lyricTextWeight: number = 400 @State lyricTextWeightPip: number = 400 @State currentSwiperIndex: number = 0 @State blurValue: number = 0 //背景模糊 @State bgBrightness: number = 0 //背景亮度 private listScroller: ListScroller = new ListScroller() private playListScroller: Scroller = new Scroller() @State isOneKeyTags:boolean = false @State isFixMessy:boolean = false @StorageProp('isLandscape') @Watch('onIsLandscapeChange') isLandscape: boolean = false; @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight()); @StorageProp('windowWidth') windowWidth: number = 0; @StorageProp('windowHeight') windowHeight: number = 0; @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false; @StorageProp('curDisplayIsHiCar') curDisplayIsHiCar: boolean = false; private UNKONWN:string = ''; private listArea: Area = { width: 0, height: 0, position: {}, globalPosition: {} } @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET; //瀑布流的列数横竖屏动态切换 onIsLandscapeChange() { if(this.isLandscape){ this.columns = 4 }else{ this.columns = PreferencesUtil.getNumberSync('lastColumns', 2); } } onColorModeChange() { this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK if (this.isCustomizeBg) { this.titleBarModel.setRightTitleStateNormalStyleColor(Color.Transparent) this.titleBarModel.setTitleBarBackground(Color.Transparent) this.titleBarModel.setTitleBarBottomLineColor(Color.Transparent) this.titleBarModel.setLeftTitleStateNormalStyleColor(Color.Transparent) this.tabOptions.selectedBackgroundColor = Color.Transparent } else { if (!this.isDarkMode) { this.titleBarModel.setTitleBarBackground(this.themeColor) this.titleBarModel.setTitleBarBottomLineColor(this.themeColor) this.titleBarModel.setRightTitleStateNormalStyleColor(this.themeColor) this.titleBarModel.setLeftTitleStateNormalStyleColor(this.themeColor) this.tabOptions.selectedBackgroundColor = this.themeColor } else { this.titleBarModel.setTitleBarBackground($r('app.color.title_bar_bg')) this.titleBarModel.setTitleBarBottomLineColor($r('app.color.title_bar_bg')) this.titleBarModel.setRightTitleStateNormalStyleColor($r('app.color.title_bar_bg')) this.titleBarModel.setLeftTitleStateNormalStyleColor($r('app.color.title_bar_bg')) this.tabOptions.selectedBackgroundColor = $r('app.color.silvery') } } } //歌单id发生变化的时候回调 onIDChange(){ if(this.modeType==4){ this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu')) this.updateListData(this.currentSongList, true) this.titleBarModel.setTitleName(this.currentSongListName) this.isShowTitleBar = false } } onModeChange() { this.isFavMusic = false if (this.modeType === 2 || this.modeType === 3) { this.titleBarModel.setRightIcon(null) } else { this.titleBarModel.setRightIcon(($r('app.media.add'))) } LogUtil.info('onecold onModeChange = ' + this.modeType) this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true) switch (this.modeType) { case 0: this.getSortedFiles(this.currentPath) break case 1: this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu')) this.updateListData(this.mediaKuList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.media_ku'))) break case 2: this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu')) this.updateListData(this.artistList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.artist'))) break case 3: this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu')) this.updateListData(this.albumList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.album'))) break case 4://歌单 this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu')) this.updateListData(this.currentSongList, true) this.titleBarModel.setTitleName(this.currentSongListName) this.isShowTitleBar = false break } } @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined; // @StorageLink('isPlay') isPlay: boolean = false; @StorageLink('isShowPlay') isShowPlay: boolean = false; @StorageLink('songList') songList: Array = []; @State translateY: number = 0; @State isShowSheet: boolean = false; @State isShowSheetView: boolean = false; @State isShowTimeCloseView: boolean = false; private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical }); @StorageLink('currIndex') curIndex: number = 0; @Consume isShowDrawer: boolean; @Consume offsetX: number; @State isFac: boolean = false; @State favList: Array = [] @Consume isFavMusic: boolean @State isClickedPLayAll: boolean = false; private scrollerForlist: ListScroller = new ListScroller(); private knockController: KnockController | undefined = undefined; imagesF: ImageFrameInfo[] = [ { src: $r("app.media.app_loading0") }, { src: $r("app.media.app_loading1") }, { src: $r("app.media.app_loading2") }, { src: $r("app.media.app_loading3") }, ] @State animationState: AnimationStatus = AnimationStatus.Initial // 缓存机制 private cache: Map> = new Map(); private stringCache: Map = new Map(); // 新增字符串缓存 // 持久化缓存 private async saveCacheToStorage() { const cacheArray = Array.from(this.cache.entries()); const stringCacheArray = Array.from(this.stringCache.entries()); await PreferencesUtil.putSync('music_cache', JSON.stringify(cacheArray)); await PreferencesUtil.putSync('music_string_cache', JSON.stringify(stringCacheArray)); } // 载入缓存 private async loadCacheFromStorage() { const cacheString = await PreferencesUtil.getStringSync('music_cache', ''); const stringCacheString = await PreferencesUtil.getStringSync('music_string_cache', ''); // 载入字符串缓存 if (cacheString) { const cacheArray: [string, Array][] = JSON.parse(cacheString); this.cache = new Map(cacheArray); } if (stringCacheString) { const stringCacheArray: [string, string][] = JSON.parse(stringCacheString); this.stringCache = new Map(stringCacheArray); } } // 查找缓存 private findCache(key: string): Array | undefined { return this.cache.get(key); } // 查找字符串缓存 private findStringCache(key: string): string | undefined { return this.stringCache.get(key); } // 添加缓存 private addCache(key: string, value: Array) { this.cache.set(key, value); } // 添加字符串缓存 private addStringCache(key: string, value: string) { this.stringCache.set(key, value); } // 删除缓存 private deleteCache(key: string) { this.cache.delete(key); this.saveCacheToStorage() } // 删除字符串缓存 private deleteStringCache(key: string) { this.stringCache.delete(key); } // 多选机制 @State selectedFiles: Array = []; @State isMultiSelect: boolean = false; @State isAllSelected: boolean = false; @State appName: string = '' @State isHasDir: boolean = false @State packName: string = '' context = this.getUIContext().getHostContext() as common.UIAbilityContext @State titleBarModel: TitleBar.Model = new TitleBar.Model() .setTitleTextStyle(FontStyle.Normal) .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT) .setLeftIcon($r('app.media.menu')) .setLeftIconWidth(26) .setLeftIconHeight(26) .setTitleName('首页') .setTitleFontSize(18) .setTitleFontColor(Color.White) .setTitleBarBackground(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .setTitleBarBottomLineColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .setLeftTitleStateNormalStyleColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .setRightIcon($r('app.media.add')) .setOnRightClickListener(() => { this.showSheelDialog() }) // .setOnTitleClickListener(() => { // this.showPupDialog() // }) .setOnLeftClickListener(() => { this.doSwipBack() }) doSwipBack() { this.isZero = false if (this.modeType !== 0 && this.isCanBack) { this.onModeChange() this.isCanBack = false this.titleBarModel.setLeftIconMain($r('app.media.menu')) // 返回专辑列表时恢复滚动偏移量,支持列表和网格 setTimeout(() => { if (this.modeType == 2) { if (this.isGridMusic||this.twoFingerType==4) { let lastOffset = this.mScrollMap.get('lastGridArtistScrollOffset') ?? 0 let lastWaterOffset = this.mScrollMap.get('lastWaterArtistScrollOffset') ?? 0 if(this.twoFingerType==4){ this.waterScroller.scrollTo({ xOffset: 0, yOffset: lastWaterOffset }) }else{ this.scroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } else { let lastOffset = this.mScrollMap.get('lastListArtistScrollOffset') ?? 0 this.listScroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } else if (this.modeType == 3) { if (this.isGridMusic||this.twoFingerType==4) { let lastOffset = this.mScrollMap.get('lastGridAlBumScrollOffset') ?? 0 let lastWaterOffset = this.mScrollMap.get('lastWaterAlBumScrollOffset') ?? 0 if(this.twoFingerType==4){ this.waterScroller.scrollTo({ xOffset: 0, yOffset: lastWaterOffset }) }else{ this.scroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } else { let lastOffset = this.mScrollMap.get('lastListAlBumScrollOffset') ?? 0 this.listScroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } }, 200) return } if (this.isHistory || this.isFavMusic) { this.isHistory = false this.getSortedFiles(this.currentPath) return } if (this.currentPath === this.rootPath || this.modeType !== 0) { animateTo({ duration: 555 }, () => { // 动画闭包内控制Image组件的出现和消失 this.isShowDrawer = !this.isShowDrawer this.offsetX = 0 }) return } //返回上一级歌单的滚动位置 setTimeout(() => { if (this.modeType == 0) { if (this.isGridMusic||this.twoFingerType==4) { let lastOffset = this.mScrollMap.get('lastGridDirScrollOffset') ?? 0 let lastWaterOffset = this.mScrollMap.get('lastWaterDirScrollOffset') ?? 0 if(this.twoFingerType==4){ this.waterScroller.scrollTo({ xOffset: 0, yOffset: lastWaterOffset }) }else{ this.scroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } else { let lastOffset = this.mScrollMap.get('lastListDirScrollOffset') ?? 0 this.listScroller.scrollTo({ xOffset: 0, yOffset: lastOffset }) } } }, 300) this.currentPath = Utility.getParentDirectory(this.currentPath) Logger.info('this.currentPath2 = ' + this.currentPath) this.getSortedFiles(this.currentPath) } // 组件生命周期 aboutToAppear() { // 初始化 PlaylistTable this.playlistTable = new PlaylistTable(getContext(this)) this.sonTwoFingerType = PreferencesUtil.getNumberSync('sonTwoFingerType', 3); this.columns = PreferencesUtil.getNumberSync('lastColumns', 2); if(PreferencesUtil.getBooleanSync('isFirstTiped',true)){ this.tipPopup = !this.tipPopup PreferencesUtil.putSync('isFirstTiped',false) } this.initSetting() this.UNKONWN = Utility.resourceToString(this.context, $r('app.string.unknown')); this.topRectHeight = px2vp(AppUtil.getStatusBarHeight()); Utility.getAppName(getContext(this)).then((appName: string) => { this.appName = appName }) this.packName = AppUtil.getBundleName() this.tabSelectedIndexes[0] = this.modeType//SegmentButton的索引 this.loadCacheFromStorage(); // 加载文件夹缓存 // 在首页初始化时读取媒体库,艺术家,专辑等缓存数据 this.initLoadCache() this.mkDownLoadDir() let eventMusic: emitter.InnerEvent = { eventId: EventConstants.EVENT_AUDIO_OPEN } // 监听广播事件(打开其他应用处理) emitter.on(eventMusic, (eventData: emitter.EventData) => { this.saveVideoDatas([eventData.data?.message], true) }); //侧滑广播接收时间 let eventUpdateSwipeBack: emitter.InnerEvent = { eventId: EventConstants.EVENT_SWIPE_BACK_UPDATE } // 监听广播事件(打开其他应用处理) emitter.on(eventUpdateSwipeBack, (eventData: emitter.EventData) => { animateTo({ duration: 555 }, () => { this.doSwipBack() }) }); //ScanFilePage广播接收时间 let eventScanUpdate: emitter.InnerEvent = { eventId: EventConstants.EVENT_SCAN_UPDATE } // 监听ScanFilePage广播事件(更新数据库) emitter.on(eventScanUpdate, (eventData: emitter.EventData) => { this.doUpdateData() }); // 监听歌单播放请求事件 let eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY } emitter.on(eventPlaylistPlay, (eventData: emitter.EventData) => { Logger.info('heanup eventPlaylistPlay received - full eventData: ' + JSON.stringify(eventData)) Logger.info('heanup eventPlaylistPlay received - eventData.data: ' + JSON.stringify(eventData.data)) const data = eventData.data as Record if (!data) { Logger.error('heanup eventPlaylistPlay: eventData.data is undefined or null') return } // 检查歌单播放数据结构 if (data.playlistId && data.songFilePaths && data.startIndex !== undefined) { Logger.info('heanup eventPlaylistPlay: 接收到歌单播放数据') // 手动构建数据对象以避免类型转换问题 const playlistData: PlaylistEventData = { playlistId: data.playlistId as string, playlistName: data.playlistName as string, songCount: data.songCount as number, startIndex: data.startIndex as number, songFilePaths: data.songFilePaths as string[] } // 根据文件路径重新构建歌曲列表 this.handlePlaylistPlayRequest( playlistData.playlistId, playlistData.playlistName, playlistData.songFilePaths, playlistData.startIndex ) return } Logger.error('heanup eventPlaylistPlay: 接收到无效的数据结构') }); let eventSetting: emitter.InnerEvent = { eventId: EventConstants.EVENT_SETTING_UPDATE } // 监听广播事件(通用设置配置更新) emitter.on(eventSetting, (eventData: emitter.EventData) => { this.doChangeSetting() }); let event: Callback = (event) => { // LogUtils.getInstance().LOGI(`onecold 与其他应用音频冲突 被截断点event: ${JSON.stringify(event)}`); console.info(`onecold 与其他应用音频冲突 被截断点event: ${JSON.stringify(event)}`) this.savePlaybackPosition() if (event.hintType === InterruptHintType.INTERRUPT_HINT_PAUSE) { this.pause(); } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_RESUME) { console.info(`onecold audioInterrupt startPlayOrResumePlay: ${JSON.stringify(event)}`) console.info('onecold startPlayOrResumePlay 514') this.startPlayOrResumePlay(); } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_STOP) { this.stop(); } } this.mIjkMediaPlayer.on('audioInterrupt', event); // 音频设备断连回调处理 // let deviceChangeEvent: Callback = (event) => { // LogUtils.getInstance().LOGI(`Heanup deviceChange event: ${JSON.stringify(event)}`); // if (event.reason === DeviceChangeReason.REASON_OLD_DEVICE_UNAVAILABLE) { // 音频设备断开连接 // this.pause(); // } // } // this.mIjkMediaPlayer.on('deviceChange', deviceChangeEvent); this.makeWorker() //折叠屏的屏幕显示模式变化 display.on('foldDisplayModeChange', (data) => { this.doChangeBarHeight() }); display.on('foldStatusChange', (data) => { Logger.info('onecold foldStatusChange ') }); this.isCoverRectangle = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_RECTANGLE, true) // 读取 themeMode 并同步 currentColorMode this.themeMode = PreferencesUtil.getNumberSync(SettingPage.THEME_MODE, 0) this.applyThemeMode(this.themeMode) let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR); AppStorage.setOrCreate('themeColor', themeColor); this.themeColor = themeColor; this.doChangeSetting() this.windowClass.on('windowSizeChange', (size) => { LogUtil.info('onecold windowSizeChange') this.doChangeBarHeight() let viewWidth = px2vp(size.width); let viewHeight = px2vp(size.height); if(this.isPhoneLan()){ this.is_auto_hide_progress = false if(this.isHiCar()){ return } setTimeout(() => { this.is_auto_hide_progress = true }, 6000) }else{ this.startAutoHide() } }); this.eventHub.on('onStateChange', (fg: boolean) => { if (fg && this.curState === 'STARTED') { this.stopPip(); } }); } //载入媒体库,艺术家,专辑等缓存 initLoadCache(){ this.mediaKuCount = PreferencesUtil.getNumberSync('mediaKuCount', 0) this.artistCount = PreferencesUtil.getNumberSync('artistCount', 0) this.albumCount = PreferencesUtil.getNumberSync('albumCount', 0) let cachedListStr:string = PreferencesUtil.getStringSync('mediaKuList', '[]'); let cachedList = JSON.parse(cachedListStr) as Array; if(ArrayUtil.isNotEmpty(cachedList)){ this.mediaKuList = cachedList if(this.modeType==1){ this.updateListData(this.mediaKuList) } } let artistListStr:string = PreferencesUtil.getStringSync('artistList', '[]'); let artistCachedList = JSON.parse(artistListStr) as Array; if(ArrayUtil.isNotEmpty(artistCachedList)){ this.artistList = artistCachedList let artistMapStr:string = PreferencesUtil.getStringSync('artistMap', '[]'); const parsedArray = JSON.parse(artistMapStr) as [string, VideoItem[]][]; this.artistMap = new Map(parsedArray); // this.artistMap = JSON.parse(artistMapStr) as Map; if(this.modeType==2){ this.updateListData(this.artistList) } } let albumListStr:string = PreferencesUtil.getStringSync('albumList', '[]'); let albumCachedList = JSON.parse(albumListStr) as Array; if(ArrayUtil.isNotEmpty(albumCachedList)){ this.albumList = albumCachedList let albumMapStr:string = PreferencesUtil.getStringSync('albumMap', '[]'); const parsedArray = JSON.parse(albumMapStr) as [string, VideoItem[]][]; this.albumMap = new Map(parsedArray); if(this.modeType==3){ this.updateListData(this.albumList) } } } startAutoHide() { if(this.isHiCar()){ return } if(PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_HIDE_PROGRESS,false)){ console.info('onecold startAutoHide') this.is_auto_hide_progress = false setTimeout(() => { this.is_auto_hide_progress = true }, 6000) } } applyThemeMode(mode: number) { let colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET if (mode === 1) { colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT } else if (mode === 2) { colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_DARK } AppStorage.setOrCreate('currentColorMode', colorMode) AppStorage.setOrCreate('isDarkMode', colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) PreferencesUtil.putSync(SettingPage.THEME_MODE, mode) AppStorage.setOrCreate('themeMode', mode) this.isDarkMode = colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK // 只有强制浅色/深色时才主动设置 const context = getContext(this) as common.UIAbilityContext context.getApplicationContext().setColorMode(colorMode) } initSetting() { this.sortType = PreferencesUtil.getNumberSync(SettingPage.SORT_TYPE, 4) this.isCustomizeBg = PreferencesUtil.getBooleanSync(SettingPage.IS_CUSTOMIZE_BG, false) this.customizeBgPath = PreferencesUtil.getStringSync(SettingPage.IS_CUSTOMIZE_BG_PATH, '') this.isGridMusic = PreferencesUtil.getBooleanSync(SettingPage.IS_GRID_MUSIC, false) this.isScrollHide = PreferencesUtil.getBooleanSync(SettingPage.IS_SCROLL_HIDE, false) this.isSameTimePlay = PreferencesUtil.getBooleanSync(SettingPage.IS_SAMETIME_PLAY, false) this.isSavePlayMode = PreferencesUtil.getBooleanSync(SettingPage.iS_SAVE_PLAY_MODE, true) this.blurValue = PreferencesUtil.getNumberSync(SettingPage.CUSTOMIZE_BG_BLUR, 0) this.bgBrightness = PreferencesUtil.getNumberSync(SettingPage.BG_BRIGHTNESS, 0) this.isStartAutoPlay = PreferencesUtil.getBooleanSync(SettingPage.iS_START_AUTO_PLAY, false) this.isMemoryLastPlay = PreferencesUtil.getBooleanSync(SettingPage.iS_MEMORY_LAST_PLAY, false) this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true) this.isShowAllBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_ALLBAR, true) this.twoFingerType = PreferencesUtil.getNumberSync(SettingPage.TWO_FINGER_TYPE, 3) this.isCoverTop = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_TOP, true) this.isCircleBtn = PreferencesUtil.getBooleanSync(SettingPage.IS_CIRCLE_BTN, true) this.isShowPlayPageBack = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_PLAYPAGE_BACK, true) this.isCoverTopBig = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_TOP_BIG, false) this.isShowSingleLineLyric = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_SLLYRIC, false) this.longPressSpeed = PreferencesUtil.getNumberSync(SettingPage.LONG_PRESS_SPEED, 3) this.isPlayListBgGrass = PreferencesUtil.getBooleanSync(SettingPage.IS_PLAYLIST_BG_GRASS, true) this.isShowHeader = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_HEADER, true) this.volume = PreferencesUtil.getNumberSync('DefalutVolume', this.volume) this.isSwipe = PreferencesUtil.getBooleanSync(SettingPage.IS_SWIPE, true) this.openSkipSongAnimate = PreferencesUtil.getBooleanSync(SettingPage.OPEN_SKIPSONG_ANIMATE, true) this.volumeSmall = PreferencesUtil.getBooleanSync('volumeSmall', false) this.autoParseMusicName = PreferencesUtil.getBooleanSync('autoParseMusicName', true) this.isShowFileName = PreferencesUtil.getBooleanSync('isShowFileName', false) this.isLongNameRoLL = PreferencesUtil.getBooleanSync('isLongNameRoLL', true) this.fastForwardSeconds = PreferencesUtil.getStringSync('fastForwardSeconds', '10') this.isShowBackFast = PreferencesUtil.getBooleanSync('isShowBackFast', false) if(this.volumeSmall){ this.volume = 0.5 }else{ this.volume = 1 } //如果是hicar连接状态,这些都是false if(this.isHiCar()){ this.isShowTitleBar = false//不显示导航栏 this.columns=4 if(!(this.windowWidth>1800&&this.windowHeight>=1200))//小屏幕不可以Grid { this.isShowHeader = false//二级菜单不显示头部 this.isGridMusic= false//用list不用网格 this.isCoverTopBig = false } this.isShowPlayPageBack = true //播放页返回键要显示,方便驾驶员操作 } if (this.isSavePlayMode) { this.playType = PreferencesUtil.getNumberSync('musicPlayType', 0) } if (this.isCustomizeBg) { this.titleBarModel.setRightTitleStateNormalStyleColor(Color.Transparent) this.titleBarModel.setTitleBarBackground(Color.Transparent) this.titleBarModel.setTitleBarBottomLineColor(Color.Transparent) this.titleBarModel.setLeftTitleStateNormalStyleColor(Color.Transparent) } else { if (!this.isDarkMode) { this.titleBarModel.setTitleBarBackground(this.themeColor) this.titleBarModel.setTitleBarBottomLineColor(this.themeColor) this.titleBarModel.setRightTitleStateNormalStyleColor(this.themeColor) this.titleBarModel.setLeftTitleStateNormalStyleColor(this.themeColor) } else { this.titleBarModel.setTitleBarBackground($r('app.color.title_bar_bg')) this.titleBarModel.setTitleBarBottomLineColor($r('app.color.title_bar_bg')) this.titleBarModel.setRightTitleStateNormalStyleColor($r('app.color.title_bar_bg')) this.titleBarModel.setLeftTitleStateNormalStyleColor($r('app.color.title_bar_bg')) } } } doChangeSetting() { this.initSetting() this.deleteCache(this.currentPath) if (this.modeType === 0) { this.getSortedFiles(this.currentPath) } } doChangeBarHeight() { if (this.currentWidthBreakpoint !== WidthBreakpoint.WIDTH_SM || (this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_MD && this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_SM)) { this.setBarHeightNormal() } } onPageShow() { this.knockController?.immersiveListening(); app.setImageCacheCount(100); // 设置解码前图片数据内存缓存上限为100MB (100MB=100*1024*1024B=104857600B) app.setImageRawDataCacheSize(104857600); Logger.info('onecold onPageShow currentBreakpoint= ' + this.currentBreakpoint) } //开启线程查看各个数据库 makeWorker() { setTimeout(() => { if (PreferencesUtil.getBooleanSync('isFirstApp', true)) { ToastUtil.showToast('请到文件扫描页面导入音乐!') //发送worker通知扫描文件入库,用户反馈4000多首用这个方法扫描会闪退。 // workerInstance.postMessage({ // code: 1, // data1: this.context, // data2: this.rootPath, // data3: this.lockPath, // data4: PreferencesUtil.getStringSync('COVER_API', ''), // data5: PreferencesUtil.getBooleanSync('autoParseMusicName', true), // }); PreferencesUtil.putSync('isFirstApp', false) } else { workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName }); workerInstance.postMessage({ code: 3, data: this.context }); workerInstance.postMessage({ code: 4, data: this.context }); this.getHistoryList(false) } }, 1000) workerInstance.onmessage = (e: MessageEvents): void => { switch (e.data.code) { case 101: //第一次扫描文件夹入库 ToastUtil.showToast('刷新同步数据库成功!') Logger.info('onecold 扫描数据库结束 this.mediaKuList length= ' + this.mediaKuList.length) this.doUpdateData() break; case 102: //查询媒体库列表 this.mediaKuList = e.data.data Logger.info('onecold 查询媒体库列表 this.mediaKuList length= ' + this.mediaKuList.length) Utility.doSortListAscending(this.mediaKuList) // AppStorage.setOrCreate('mediaKuList', this.mediaKuList); if (this.modeType === 1) { this.updateListData(this.mediaKuList) } PreferencesUtil.putSync('mediaKuCount', this.mediaKuList.length) // 处理媒体库前100条记录,列表长度并存储到PreferencesUtil if (ArrayUtil.isNotEmpty(this.mediaKuList)&&this.mediaKuList.length > 100) { // 只保存前100条记录到缓存中 let truncatedList = this.mediaKuList.slice(0, 100); PreferencesUtil.putSync('mediaKuList', JSON.stringify(truncatedList)); } else { PreferencesUtil.putSync('mediaKuList', JSON.stringify(this.mediaKuList)); } break; case 103: //收到查询艺术家列表 this.artistMap = e.data.data1; this.artistList = e.data.data2; PreferencesUtil.putSync('artistCount', this.artistList.length) // 处理艺术家前100条记录,列表长度并存储到PreferencesUtil if (ArrayUtil.isNotEmpty(this.artistList)&&this.artistList.length > 100) { // 只保存前100条记录到缓存中 let truncatedList = this.artistList.slice(0, 100); PreferencesUtil.putSync('artistList', JSON.stringify(truncatedList)); } else { PreferencesUtil.putSync('artistList', JSON.stringify(this.artistList)); } // 处理并缓存前60条艺术家数据 const sortedEntries = Array.from(this.artistMap.entries()) .sort((a, b) => b[1].length - a[1].length) .slice(0, 60); PreferencesUtil.putSync('artistMap', JSON.stringify(sortedEntries)); break; case 104: //收到查询专辑列表 this.albumMap = e.data.data1; this.albumList = e.data.data2 //查询完毕在启动是否自动播放 if (this.isStartAutoPlay&&this.isCanAuto) { this.isCanAuto = false console.info('onecold startPlayOrResumePlay 757') this.startPlayOrResumePlay() } PreferencesUtil.putSync('albumCount', this.albumList.length) // 处理专辑前100条记录,列表长度并存储到PreferencesUtil if (ArrayUtil.isNotEmpty(this.albumList)&&this.albumList.length > 100) { // 只保存前100条记录到缓存中 let truncatedList = this.albumList.slice(0, 100); PreferencesUtil.putSync('albumList', JSON.stringify(truncatedList)); } else { PreferencesUtil.putSync('albumList', JSON.stringify(this.albumList)); } const mapArrayAlbum = Array.from(this.albumMap.entries()) .sort((a, b) => b[1].length - a[1].length) .slice(0, 60); PreferencesUtil.putSync('albumMap', JSON.stringify(mapArrayAlbum)); break; } }; // 在调用terminate后,执行onexit workerInstance.onexit = (code) => { console.log("main thread terminate"); } //若Worker处于已销毁或正在销毁等非运行状态时,调用其功能接口,会抛出相应的错误。 // 使用Worker模块时,需要在主线程中注册onerror接口,否则当worker线程出现异常时会发生jscrash问题。 workerInstance.onerror = (err: ErrorEvent) => { console.log("onerror" + err.message); } // let context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; //碰一碰分享的监听 this.knockController = KnockController.getInstance(this.context); this.knockController?.immersiveListening(); } onPageHide(): void { this.knockController?.immersiveDisableListening(); } @State isFirstStartPlay: boolean = false //获取download_path async mkDownLoadDir() { const documentViewPicker = new picker.DocumentViewPicker() let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD }) this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path this.currentPath = this.rootPath this.lockPath = this.rootPath + '/' + LocalMusic.STR_LOCK_VIDEO this.favPath = this.rootPath + '/' + LocalMusic.STR_FAC_VIDEO this.historyPath = this.rootPath + '/' + LocalMusic.STR_HISTORY_MUSIC Logger.info('onecold rootPath = ' + this.rootPath) Logger.info('onecold lockPath = ' + this.lockPath) if (!FileUtil.accessSync(this.lockPath)) { FileUtil.mkdirSync(this.lockPath) } if (!FileUtil.accessSync(this.favPath)) { FileUtil.mkdirSync(this.favPath) } if (!FileUtil.accessSync(this.historyPath)) { FileUtil.mkdirSync(this.historyPath) } this.getSortedFiles(this.rootPath).then(() => { this.isFavMusic = false //穿山甲 // this.loadBannerAd(CSJUtil.getBannerID()) this.songList = PreferencesUtil.getSync('LastMusicList', []) as Array this.currentSong = PreferencesUtil.getSync('LastMusicInfo', undefined) as VideoItem if (ArrayUtil.isEmpty(this.songList)) { this.songList = this.getCurFileList() } else { this.curIndex = Utility.getIndexFromList(this.songList, this.currentSong.filePath) } if (ArrayUtil.isNotEmpty(this.songList)) { this.isFirstStartPlay = true this.sonDataSource.pushArrayData(this.songList) if (this.currentSong === undefined) { this.isFirstStartPlay = false this.currentSong = this.songList[0] } this.videoUrl = this.currentSong.filePath this.name = this.currentSong.name this.cover = this.currentSong.pixelMapPath AppStorage.setOrCreate('currentSong',this.currentSong) ; } else { this.name = '空空如也' } }) this.getFavList(false) } getFavList(isFavRefresh: boolean) { this.table.queryByisFav(1, async (result: VideoItem[]) => { this.favList = result if (isFavRefresh || this.isFavMusic) { //如果是我的收藏,更新我的收藏数据 this.videoLocalList = this.favList this.updateListData(this.videoLocalList) } }) } getHistoryList(isRefresh: boolean) { this.table.queryRecentPlayedRecords(50, async (result: VideoItem[]) => { this.historyList = result // for (let i = 0; i < this.historyList.length; i++) { // // console.info(`onecold gengxin 1: ${this.historyList[i].name}`); // console.info(`onecold gengxin 1: ${this.historyList[i].lastPlayedStr}`); // } if (isRefresh) { this.videoLocalList = this.historyList this.updateListData(this.videoLocalList, true) } }) } // 组件消失生命周期 aboutToDisappear() { console.info('LifeCycleComponent aboutToDisappear'); this.knockController?.immersiveDisableListening(); this.curIndex = 0 emitter.off(EventConstants.EVENT_AUDIO_OPEN); emitter.off(EventConstants.EVENT_SCAN_UPDATE); emitter.off(EventConstants.EVENT_SWIPE_BACK_UPDATE); emitter.off(EventConstants.EVENT_SETTING_UPDATE); this.mDestroyPage = true; this.mIjkMediaPlayer.setScreenOnWhilePlaying(false); if (this.CONTROL_PlayStatus != PlayStatus.INIT) { this.stop(); } this.mIjkMediaPlayer.off('audioInterrupt'); if (workerInstance) { workerInstance.terminate(); } this.stopPip(); this.destroyPipController() } async getSortedFiles(curPath: string, isWorkerPost?: boolean, destPath?: string, isOpen?: boolean) { this.isFavMusic = false console.info('onecold getSortedFiles 1 curPath '+curPath) if (isWorkerPost) { workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName }); } if (this.modeType !== 0) { this.openMusic(isOpen, destPath) return } console.info('onecold getSortedFiles 2') this.isCanBack = false this.currentPath = curPath console.info('onecold getSortedFiles this.currentPath ='+this.currentPath) this.titleBarModel.setTitleName(getFileDirName(this.currentPath, this.rootPath)) if (curPath === this.rootPath) { this.titleBarModel.setLeftIconMain($r('app.media.menu')) } else { this.currentTitleName = getFileDirName(this.currentPath, this.rootPath) if (this.rootPath === Utility.getParentDirectory(curPath)) { this.titleBarModel.setLeftIconMain($r('app.media.left_back_white')) } } // Get the current file list and calculate its hash this.fileList = FileUtil.listFileSync(curPath); const currentHash = simpleHash(this.fileList); // Check cache const cachedValue = this.findCache(curPath); Logger.info('onecold cachedValue = ' + cachedValue) const cachedHash = this.findStringCache(curPath + '_hash'); const cachedDirList = this.findCache(curPath + "_dirList"); if (cachedValue !== undefined) { // this.videoLocalList = cachedValue; Logger.info('onecold 有缓存 cachedValue = ' + cachedValue) if (this.currentPath != this.rootPath) { //第一个封面赋值给currentTitleCover this.currentTitleCover = Utility.getFirstCoverFromList(cachedValue) } this.updateListData(cachedValue) } if (cachedDirList !== undefined) { this.dirList = cachedDirList } //通过当前hash值和缓存hash判断list有没有变化 if (cachedValue && cachedHash === currentHash) { return; } this.fileList = FileUtil.listFileSync(curPath) let directories: Array = []; let files: Array = []; for (let i = 0; i < this.fileList.length; i++) { console.info(`The name of file: ${this.fileList[i]}`); let path = curPath + '/' + this.fileList[i] if (FileUtil.isDirectory(path)) { let item: VideoItem = new VideoItem(this.fileList[i].toString(), this.fileList[i].toString(), path, CommonConstants.TYPE_IS_DIR, 0, '') if (this.isShowDir(item.name)) { directories.push(item); } // directories.sort((a, b) => a.cTime.localeCompare(b.cTime)); // Utility.doSortListAscending(directories) } else { if (this.currentPath === this.lockPath&&Utility.isMeidaByExtension(path)) { let item: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, path, CommonConstants.TYPE_LOCAL, false); if (!path.endsWith('.lrc') && Utility.isMeidaByExtension(path) && !path.endsWith('.srt')) { files.push(item); // files.sort((a, b) => a.cTime.localeCompare(b.cTime)); // this.doSortListAscending(files) } } } } this.table.queryByParentPath(curPath, async (result: VideoItem[]) => { if (this.currentPath !== this.lockPath) { files = result; } files.sort((a, b) => a.cTime.localeCompare(b.cTime)); Utility.doSortListAscending(files) if (this.currentPath === this.rootPath) { this.dirList = directories this.addCache(curPath + "_dirList", this.dirList); } //插入广告先注释掉 // if (this.isShowAd && ArrayUtil.isNotEmpty(directories)) { // let adItem: VideoItem = new VideoItem('', '', '', CommonConstants.TYPE_IS_CSJAD, 0, '') // directories.push(adItem) // } // 合并文件夹和文件,文件夹在前,文件在后,广告在中间 this.videoLocalList = directories.concat(files); this.updateListData(this.videoLocalList) if (ArrayUtil.isNotEmpty(files)) { if (this.currentPath != this.rootPath) { if (files) { this.currentTitleCover = Utility.getFirstCoverFromList(files) } } // 缓存结果 this.addCache(curPath, this.videoLocalList); await this.saveCacheToStorage(); // 保存缓存至本地存储 this.addStringCache(curPath + '_hash', currentHash); } console.info(`onecold gengxin 1: ${this.videoLocalList.length}`); // for (let i = 0; i < this.videoLocalList.length; i++) { // // console.info(`onecold gengxin 1: ${this.videoLocalList[i].pixelMapPath}`); // // } // this.setButtonStatus() this.openMusic(isOpen, destPath) }); } //第三方app打开天天静听导入音乐后播放 openMusic(isOpen?: boolean, destPath?: string){ if (isOpen&&destPath) { if (destPath === undefined) { // 处理 destPath 为 undefined 的情况 return } if (destPath.endsWith('.lrc')) { ToastUtil.showToast('导入歌词成功!请注意歌词文件和歌曲要同个歌单路径。') if (StrUtil.isNotEmpty(this.videoUrl)) { let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc' this.initLyric(lyricPath); } return } let item2 = new VideoItem(Utility.getMediaNameByUri(destPath), destPath, destPath, 0, 0, '') this.doPlay(item2,0,false,true) this.isShowPlay = true } } //扫描文件夹入库 async scanDirectory(curPath: string): Promise { const files = FileUtil.listFileSync(curPath); let mediaItems: VideoItem[] = []; Logger.info('onecold scanDirectory start ') for (const file of files) { const fPath = `${curPath}/${file}`; if (fPath === this.lockPath) { Logger.info(`Skipping locked path: ${fPath}`); continue; } if (FileUtil.isDirectory(fPath)) { // Recursively scan subdirectories const subDirItems = await this.scanDirectory(fPath); mediaItems = mediaItems.concat(subDirItems); } else { Logger.info('onecold scanDirectory filePath = ' + fPath) // Process media files if (!fPath.endsWith('.lrc') && Utility.isMeidaByExtension(fPath) && !fPath.endsWith('.srt')) { let mediaItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, fPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); mediaItems.push(mediaItem); this.table.insert(mediaItem, (id: number) => { }); } } } return mediaItems; } updateListData(mList: Array, noSort?: boolean) { animateTo({ duration: 888 }, () => { this.opacityItem = 0; }); setTimeout(() => { this.videoLocalList = mList; if (!noSort) { if (this.modeType === 0 || this.modeType === 1) { this.sortType = PreferencesUtil.getNumberSync(SettingPage.SORT_TYPE, 4) this.doSortType(this.sortType) } } this.dataSource.pushArrayData(this.videoLocalList) animateTo({ duration: 888 }, () => { this.opacityItem = 1; }); this.setButtonStatus() }, 200); } //排序模式和多选模式 showPupDialog() { if (this.modeType === 2 || this.modeType === 3) { this.titleBarModel.setRightIcon(null) } else { this.titleBarModel.setRightIcon(($r('app.media.add'))) } let dataBean = new BubbleBean() dataBean.data = ["首页", "媒体库", "艺术家", "专辑", "同步数据"] dataBean.color = this.themeColor dataBean.modeType = this.modeType dataBean.imageRes = [$r('app.media.home_press'), $r('app.media.kp_music'), $r('app.media.music_menu'), $r('app.media.llq'), $r('app.media.refresh')] let pup: XPopup; if (this.isDarkMode) { pup = XPopup.Builder() .setPopupPosition(PopupPosition.BOTTOM) .atView('pup_positon_music') .setBackgroundColor(Color.Black) .asBubble(wrapBuilder(customPopupBuilder), dataBean) .show() } else { pup = XPopup.Builder() .setPopupPosition(PopupPosition.BOTTOM) .atView('pup_positon_music') .setBackgroundColor(Color.White) .asBubble(wrapBuilder(customPopupBuilder), dataBean) .show() } dataBean.onItemClick = (i) => { XPopup.dismissTop() switch (i) { case 0: //首页 this.modeType = 0 this.getSortedFiles(this.currentPath) break case 1: this.titleBarModel.setLeftIconMain($r('app.media.menu')) this.modeType = 1 this.updateListData(this.mediaKuList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.media_ku'))) break case 2: this.modeType = 2 this.titleBarModel.setLeftIconMain($r('app.media.menu')) this.updateListData(this.artistList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.artist'))) break case 3: this.modeType = 3 this.titleBarModel.setLeftIconMain($r('app.media.menu')) this.updateListData(this.albumList) this.titleBarModel.setTitleName(Utility.resourceToString(this.context, $r('app.string.album'))) break case 4: //同步数据 // this.asyncCurrentPathData() break } } } getCurFileList(): Array { if(this.isSearchMode){ return this.filteredList } let files: Array = []; if (ArrayUtil.isNotEmpty(this.videoLocalList)) { for (let i = 0; i < this.videoLocalList.length; i++) { if (this.videoLocalList[i].type === CommonConstants.TYPE_LOCAL) { files.push(this.videoLocalList[i]); } } } return files } setButtonStatus() { if (ArrayUtil.isNotEmpty(this.videoLocalList)) { this.isZero = false } else { this.isZero = true } if (this.currentPath !== this.rootPath && ArrayUtil.isNotEmpty(Utility.getGlobalNameList(this.videoLocalList, CommonConstants.TYPE_IS_DIR))) { this.isHasDir = true //不为空,说明有子文件夹 } else { this.isHasDir = false } } showRankDialog() { if ((this.modeType === 2&&!this.isCanBack) || (this.modeType === 3&&!this.isCanBack)) { //动作面板 DialogHelper.showBottomSheetDialog({ title: "请选择排序模式", maskColor: Color.Transparent, // sheets: ["按名称升序", "按名称降序"], sheets: [ { value: '按名称升序', fontColor: $r('app.color.text_color') }, { value: '按名称降序', fontColor: $r('app.color.text_color') }, { value: '按数量升序', fontColor: $r('app.color.text_color') }, { value: '按数量降序', fontColor: $r('app.color.text_color') }, ], backgroundColor: $r('app.color.bg_card'), height: '45%', transition: AnimationHelper.transitionInDown(555), onAction: (index) => { switch (index) { case 0: Utility.doSortListAscending(this.videoLocalList,this.isShowFileName) break; case 1: Utility.doSortListDescending(this.videoLocalList,this.isShowFileName) break; case 2: this.videoLocalList.sort((a: VideoItem, b: VideoItem): number => { // 1. Sort by type first const typeOrder: number = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // 2. Then sort by artist count (numeric comparison) let sortMap:Map= new Map(); if(this.modeType === 2){ sortMap = this.artistMap }else{ sortMap = this.albumMap } const aArtistCount: number = sortMap.get(a.name)?.length || 0; const bArtistCount: number = sortMap.get(b.name)?.length || 0; return aArtistCount - bArtistCount; }); break; case 3: this.videoLocalList.sort((a: VideoItem, b: VideoItem): number => { // 1. Sort by type first const typeOrder: number = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } let sortMap:Map= new Map(); if(this.modeType === 2){ sortMap = this.artistMap }else{ sortMap = this.albumMap } const aArtistCount: number = sortMap.get(a.name)?.length || 0; const bArtistCount: number = sortMap.get(b.name)?.length || 0; return bArtistCount - aArtistCount; }); break; } this.updateListData(this.videoLocalList) } }) return } //动作面板 DialogHelper.showBottomSheetDialog({ title: "请选择排序模式", maskColor: Color.Transparent, height: '75%', // sheets: ["按艺术家升序", "按艺术家降序", "按专辑升序", "按专辑降序", "按名称升序", "按名称降序", "按添加时间升序","按添加时间降序"], sheets: [ { value: "按艺术家升序", fontColor: $r('app.color.text_color') }, { value: "按艺术家降序", fontColor: $r('app.color.text_color') }, { value: "按专辑升序", fontColor: $r('app.color.text_color') }, { value: "按专辑降序", fontColor: $r('app.color.text_color') }, { value: "按名称升序", fontColor: $r('app.color.text_color') }, { value: "按名称降序", fontColor: $r('app.color.text_color') }, { value: "按添加时间升序", fontColor: $r('app.color.text_color') }, { value: "按添加时间降序", fontColor: $r('app.color.text_color') }, ], backgroundColor: $r('app.color.bg_card'), transition: AnimationHelper.transitionInDown(555), onAction: (index) => { this.doSortType(index) PreferencesUtil.put(SettingPage.SORT_TYPE, index) this.updateListData(this.videoLocalList, true) } }) } doSortType(index: number) { switch (index) { case 0: this.videoLocalList.sort((a: VideoItem, b: VideoItem) => { // 类型排序优先级 const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // 处理艺术家可能为undefined的字符串比较 const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格 const artistB = b.artist?.trim() || ''; return artistA.localeCompare(artistB); }); break; case 1: this.videoLocalList.sort((a: VideoItem, b: VideoItem) => { // 类型排序优先级 const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // 处理艺术家可能为undefined的字符串比较 const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格 const artistB = b.artist?.trim() || ''; return artistB.localeCompare(artistA); }); break; case 2: this.videoLocalList.sort((a: VideoItem, b: VideoItem) => { // 类型排序优先级 const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // 处理专辑可能为undefined的字符串比较 const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格 const albumB = b.album?.trim() || ''; return albumA.localeCompare(albumB); }); break; case 3: this.videoLocalList.sort((a: VideoItem, b: VideoItem) => { // 类型排序优先级 const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // 处理专辑可能为undefined的字符串比较 const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格 const albumB = b.album?.trim() || ''; return albumB.localeCompare(albumA); }); break; case 4: Utility.doSortListAscending(this.videoLocalList,this.isShowFileName) break; case 5: Utility.doSortListDescending(this.videoLocalList,this.isShowFileName) break; case 6: this.videoLocalList.sort((a, b) => { // First, sort by type const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // If types are the same, sort by cTime in ascending order return a.cTime.localeCompare(b.cTime); }); break; case 7: this.videoLocalList.sort((a, b) => { // First, sort by type const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type); if (typeOrder !== 0) { return typeOrder; } // If types are the same, sort by cTime in descending order return b.cTime.localeCompare(a.cTime); }); break; } } showSheelDialog() { // if(PreferencesUtil.getBooleanSync('isFirstMusic',true)) { // this.showTips() // return // } this.showAddDialog() } showAddDialog() { if (this.modeType === 2 || this.modeType === 3) { return } DialogHelper.showActionSheetDialog({ title: "请选择添加方式(建议歌词文件一起导入)", // sheets: ["新建歌单", "手动导入", "自动导入"], sheets: [ { value: "新建歌单", fontColor: $r('app.color.text_color') }, { value: "手动导入", fontColor: $r('app.color.text_color') }, { value: "自动导入", fontColor: $r('app.color.text_color') }, { value: "全选导入", fontColor: $r('app.color.text_color') }, ], maskColor: Color.Transparent, backgroundColor: $r('app.color.bg_card'), transition: AnimationHelper.transitionInDown(555), onAction: (index) => { switch (index) { case 0: if (this.modeType === 0) { this.showMkDialog() } else { ToastUtil.showToast('请切换到首页才能新建歌单!') } break; case 1: this.callFilePickerSelectFile() break; case 2: this.showTips() break; case 3: this.goSelectMusic() break; } } }) } //拉起音频 goSelectMusic() { if (DeviceUtil.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_TABLET || DeviceUtil.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_PC || DeviceUtil.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_2IN1 ) { ToastUtil.showToast('暂时不支持该设备') return } try { let documentSelectOptions = new picker.DocumentSelectOptions(); documentSelectOptions.authMode = true documentSelectOptions.multiAuthMode = true documentSelectOptions.mergeMode = picker.MergeTypeMode.AUDIO; let documentPicker = new picker.DocumentViewPicker(this.context); documentPicker.select(documentSelectOptions).then((documentSelectResult: Array) => { if (documentSelectResult !== null && documentSelectResult !== undefined) { this.saveVideoDatas(documentSelectResult) } console.info('DocumentViewPicker.select successfully, documentSelectResult uri: ' + JSON.stringify(documentSelectResult)); }).catch((err: BusinessError) => { console.error(`DocumentViewPicker.select failed with err, code is: ${err.code}, message is: ${err.message}`); }); } catch (error) { let err: BusinessError = error as BusinessError; console.error('AudioViewPicker failed with err: ' + JSON.stringify(err)); } } async showTips() { DialogHelper.showTipsDialog({ title: '使用提示:自动导入教程', content: '用系统自带的文件管理器将:\n\n1、音频文件放入' + '\n\n我的设备/Download/' + this.appName + '\n\n2、无需繁琐的勾选和分类步骤。\n\n3、每个子文件夹将被组成一个分组。' + '\n\n4、歌词文件的文件名必须和目标歌曲文件名相同(不包含后缀名)。\n\n5、歌词文件和目标歌曲文件必须同个目录。' + '\n\n6、电脑上Download的目录是\nDownload/' + this.packName + '。\n\n7、放好文件后如果没显示点击刷新按钮。\n', onAction: (action) => { if (action == DialogAction.TWO) { PreferencesUtil.putSync('isFirstMusic', false) router.pushUrl({ url: 'pages/ScanFilePage' }); } } }) } showMkDialog() { DialogHelper.showTextInputDialog({ title: '新建歌单', maskColor: Color.Transparent, text: '', transition: AnimationHelper.transitionInDown(666), onChange: (text) => { console.error("onChange: " + text); }, onAction: (action, dialogId, content) => { // ToastUtil.showToast('content = ' +content) if (action == DialogAction.TWO) { const newName = content const newPath = this.currentPath + '/' + newName FileUtil.mkdir(newPath, true).then(() => { this.cache.delete(this.currentPath); // 删除目标路径缓存 this.getSortedFiles(this.currentPath) }).catch((error: BusinessError) => { console.error(error.message); }); } } }) } @State progress: number = 0; async saveVideoDatas(uris: string[], isOpen?: boolean) { if (ArrayUtil.isEmpty(uris)) { return; } let newUris: string[] = []; this.progress = 0 DialogHelper.showLoadingProgress({ progress: this.progress, backCancel: false, autoCancel: false, loadColor: this.themeColor, fontColor: this.themeColor }); // 计算所有文件的总大小 let totalSize = 0; for (let uri of uris) { const sourceFile = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY); totalSize += (await fileIo.stat(sourceFile.fd)).size; await fileIo.close(sourceFile.fd); } let totalRead = 0; let destPath = this.currentPath + '/' + Utility.getMediaNameByUri(uris[0]); Logger.info(TAG, 'destPath uri: ' + destPath); for (let i = 0; i < uris.length; i++) { let filePath = this.currentPath + '/' + Utility.getMediaNameByUri(uris[i]); Logger.info(TAG, 'filePath uri: ' + filePath); newUris.push(filePath); try { const sourceFile = await fileIo.open(uris[i], fileIo.OpenMode.READ_ONLY); const destFile = await fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); const buffSize = 1024 * 1024; // 1MB 缓冲区大小 const buffer = new ArrayBuffer(buffSize); let len = await fileIo.read(sourceFile.fd, buffer); while (len > 0) { await fileIo.write(destFile.fd, buffer.slice(0, len)); totalRead += len; // 计算总进度 this.progress = Math.floor((totalRead / totalSize) * 100); DialogHelper.updateLoading('正在导入', this.progress); len = await fileIo.read(sourceFile.fd, buffer); } await fileIo.close(sourceFile.fd); await fileIo.close(destFile.fd); if (this.currentPath !== this.lockPath) { //判断不是私密音乐,才入库 if (!filePath.endsWith('.lrc') && Utility.isMeidaByExtension(filePath) && !filePath.endsWith('.srt')) { let mediaItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, filePath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); this.table.insert(mediaItem, (id: number) => { // 删除目标路径缓存 this.cache.delete(this.currentPath); this.getSortedFiles(this.currentPath, true, destPath, isOpen) }); } } } catch (error) { Logger.error(TAG, 'saveVideoDatas failed with err: ' + JSON.stringify(error)); } } DialogHelper.closeLoading() this.isZero = false // setTimeout(() => { // // 删除目标路径缓存 // this.cache.delete(this.currentPath); // this.getSortedFiles(this.currentPath,true,destPath,isOpen) // }, 500); Logger.info(TAG, 'scan select video result:' + newUris) } //拉起音频 // goSelectMusic(){ // // try { // let audioSelectOptions = new picker.AudioSelectOptions(); // let audioPicker = new picker.AudioViewPicker(this.context); // audioPicker.select(audioSelectOptions).then((audioSelectResult: Array) => { // if (audioSelectResult !== null && audioSelectResult !== undefined) { // this.saveVideoDatas(audioSelectResult) // } // console.info('AudioViewPicker.select successfully, audioSelectResult uri: ' + JSON.stringify(audioSelectResult)); // }).catch((err: BusinessError) => { // // console.error('AudioViewPicker.select failed with err: ' + JSON.stringify(err)); // }); // } catch (error) { // let err: BusinessError = error as BusinessError; // console.error('AudioViewPicker failed with err: ' + JSON.stringify(err)); // } // // // // } async goSelectImage(item: VideoItem): Promise { if (!item) { return undefined; } try { let selectUris: Array = []; let photoPicker = new photoAccessHelper.PhotoViewPicker(); let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 1; const photoSelectResult = await photoPicker.select(photoSelectOptions); selectUris = photoSelectResult.photoUris; console.info('photoViewPicker.select to file succeed and uris are:' + selectUris); let file = fs.openSync(selectUris[0], fs.OpenMode.READ_ONLY); console.info('file fd: ' + file.fd); let name = await MD5.digestSync(this.videoUrl)+'.jpg' let imagePath = this.context.filesDir + FileUtil.separator + name imagePath = fileUri.getUriFromPath(imagePath) let file2 = fileIo.openSync(imagePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE) fileIo.copyFileSync(file.fd, file2.fd) fileIo.closeSync(file); fileIo.closeSync(file2); if (item.filePath == this.currentSong?.filePath) { this.cover = imagePath } if (item) { item.pixelMapPath = imagePath } this.table.updatePixelMapPath(item.filePath, imagePath, (success: boolean, error?: string) => { if (success) { this.doUpdateData() console.log("onecold 更新音乐封面成功,数据库已同步"); } else { console.error("onecold 更新音乐封面数据库失败原因: " + error); } }); LogUtil.debug("onecold this.cover =" + this.cover) return FileUtil.getFilePath(imagePath); // Return the image path here } catch (err) { console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); return undefined; } } // 拉起picker选择文件管理器 async callFilePickerSelectFile(): Promise { try { let DocumentSelectOptions = new picker.DocumentSelectOptions(); if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE) { DocumentSelectOptions.fileSuffixFilters = CommonConstants.MEDIA_FORMAT } else { DocumentSelectOptions.mergeMode = picker.MergeTypeMode.AUDIO } let documentPicker = new picker.DocumentViewPicker(); documentPicker.select(DocumentSelectOptions).then((DocumentSelectResult) => { Logger.info(TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + JSON.stringify(DocumentSelectResult)); if (DocumentSelectResult !== null && DocumentSelectResult !== undefined) { this.saveVideoDatas(DocumentSelectResult) } }).catch((err: BusinessError) => { Logger.error(TAG, 'DocumentViewPicker.select failed with err: ' + JSON.stringify(err)); }); } catch (err) { Logger.error(TAG, 'DocumentViewPicker failed with err: ' + JSON.stringify(err)); } } //重命名的对话框 showReNameDialog(item: VideoItem, index: string, filePath: string) { DialogHelper.showTextInputDialog({ title: '重命名', text: item.type === CommonConstants.TYPE_IS_DIR ? item.name : item.fileName, maskColor: Color.Transparent, backgroundColor: Color.Grey, transition: AnimationHelper.transitionInDown(666), onChange: (text) => { console.error("onChange: " + text); }, onAction: (action, dialogId, content) => { // ToastUtil.showToast('content = ' +content) if (action == DialogAction.TWO) { const newName = content const newPath = this.currentPath + '/' + newName console.log("onecold item = " + item.type); this.doReName(index, newName, filePath, newPath, item) } } }) } //剪切 移动到另个文件夹的对话框 showCutDialog(isCurrent: boolean, item: VideoItem, index: number, id: string) { Logger.info('onecold showCutDialog') let mList: Array = [] let rPath = this.rootPath if (isCurrent) { mList = Utility.getGlobalNameList(this.videoLocalList, CommonConstants.TYPE_IS_DIR); rPath = this.currentPath } else { mList = Utility.getNameList(this.dirList); rPath = this.rootPath } let dataBean = new BubbleBean() dataBean.data = mList dataBean.color = this.themeColor dataBean.onItemClick = (i) => { const newPath = rPath + '/' + mList[i] + '/' + item.fileName Logger.info('newPath = ' + newPath) FileUtil.moveFile(item.filePath, newPath, 1).then(async () => { ToastUtil.showToast('移动成功') this.table.deleteData(item, () => { }); // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap, // item.size,item.pixelMapToString,item.artist,item.album,item.fileName) let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库 this.table.insert(newItem, (id: number) => { //加入数据库 }); } this.cache.delete(this.currentPath); // 删除原路径缓存 this.cache.delete(rPath + '/' + mList[i]); // 删除目标路径缓存 this.getSortedFiles(this.currentPath, true) }).catch((error: BusinessError) => { console.error(error.message); }); XPopup.dismissTop() } let popupPosition: PopupPosition = PopupPosition.BOTTOM if (ArrayUtil.isNotEmpty(this.videoLocalList) && this.videoLocalList.length > 7) { popupPosition = PopupPosition.TOP } if (index <= 3) { popupPosition = PopupPosition.BOTTOM } let pup: XPopup if (this.isDarkMode) { pup = XPopup.Builder() .setPopupPosition(popupPosition) .atView(id)// .setHeight(this.isCoverOpacity()?100:420) .setBackgroundColor(Color.Black) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show() } else { pup = XPopup.Builder() .setPopupPosition(popupPosition) .atView(id)// .setHeight(this.isCoverOpacity()?280:420) .setBackgroundColor(Color.White) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show() } } //复制 到另个文件夹的对话框 showCopyDialog(isCurrent: boolean, item: VideoItem, index: number, id: string) { Logger.info('onecoldv showCopyDialog') let mList: Array = [] let rPath = this.rootPath if (isCurrent) { mList = Utility.getGlobalNameList(this.videoLocalList, CommonConstants.TYPE_IS_DIR); rPath = this.currentPath } else { mList = Utility.getNameList(this.dirList); rPath = this.rootPath } let dataBean = new BubbleBean() dataBean.data = mList dataBean.isCopy = true dataBean.color = this.themeColor dataBean.onItemClick = (i) => { const newPath = rPath + '/' + mList[i] + '/' + item.fileName Logger.info('newPath = ' + newPath) FileUtil.copyFile(item.filePath, newPath, 0).then(async () => { ToastUtil.showToast('复制成功') this.cache.delete(rPath + '/' + mList[i]); // 删除目标路径缓存 this.getSortedFiles(this.currentPath) // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap, // item.size,item.pixelMapToString,item.artist,item.album,item.fileName) let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库 this.table.insert(newItem, (id: number) => { //加入数据库 }); } }).catch((error: BusinessError) => { console.error(error.message); }); XPopup.dismissTop() } let popupPosition: PopupPosition = PopupPosition.BOTTOM if (ArrayUtil.isNotEmpty(this.videoLocalList) && this.videoLocalList.length > 7) { popupPosition = PopupPosition.TOP } if (index <= 3) { popupPosition = PopupPosition.BOTTOM } let pup: XPopup if (this.isDarkMode) { pup = XPopup.Builder() .setPopupPosition(popupPosition) .atView(id) .setMaxHeight('60%') .setBackgroundColor(Color.Black) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } else { pup = XPopup.Builder() .setPopupPosition(popupPosition) .atView(id) .setMaxHeight('60%') .setBackgroundColor(Color.White) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } } //重命名索引index的视频 doReName(index: string, newName: string, oldPath: string, newPath: string, info: VideoItem) { FileUtil.rename(oldPath, newPath).then(async () => { if (ArrayUtil.isNotEmpty(this.videoLocalList)) { if (info.type === CommonConstants.TYPE_IS_DIR) { //如果是文件夹先删除源数据,再添加新数据 this.table.deleteDataForParentPath(oldPath, (success: boolean, error?: string) => { if (success) { console.log(" onecold deleteDataForParentPath 文件夹重命名成功,数据库已同步"); } else { console.error(" onecold deleteDataForParentPath 文件夹重命名数据库失败原因: " + error); } }); this.scanDirectory(newPath) } else if (info.type === CommonConstants.TYPE_LOCAL) { this.table.updateRename(newName, oldPath, newPath, (success: boolean, error?: string) => { if (success) { console.log(" onecold 重命名成功,数据库已同步"); } else { console.error(" onecold 重命名数据库失败原因: " + error); } }); } let mediaItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); this.videoLocalList[index] = mediaItem; } this.cache.delete(this.currentPath) this.getSortedFiles(this.currentPath, true) }).catch((error: BusinessError) => { console.error(error.message); }); } build() { Scroll() { Column() { if (!this.isShowCoverHeader()) { Column() { Line().width('100%').height('100%') } .height(this.topRectHeight) .backgroundColor(this.isCustomizeBg ? Color.Transparent : (this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) TitleBar({ model: $titleBarModel }) .id('pup_positon_music') } Stack() { Column() { XComponent({ id: 'xcomponentId', type: 'surface', libraryname: 'ijkplayer_napi', controller: this.xcomponentController }) .onLoad((event?: object) => { if (!!event) { this.initDelayPlay(event); } }) .onKeyEvent((event?: KeyEvent) => { // If the button type is pressed, the subsequent code will not be executed; the specific button logic will be executed upon release. if (!event || event.type !== KeyType.Down) { return; } //空格键 Space key controls pause/play if (event.keyCode === KeyCode.KEYCODE_SPACE) { console.info('onecold playOrPause 1999') this.playOrPause() } // Right-click to fast forward if (event.keyCode === KeyCode.KEYCODE_DPAD_RIGHT) { this.sessionFastForwardCallback(15); } // Left-click to rewind if (event.keyCode === KeyCode.KEYCODE_DPAD_LEFT) { this.sessionRewindCallback(15); } }) .onDestroy(() => { }) .width('100%')// .width(this.videoWidth) .aspectRatio(this.videoAspectRatio) } .aspectRatio(this.videoAspectRatio) .justifyContent(FlexAlign.Center) .backgroundColor(Color.Transparent) .visibility(Visibility.Hidden) .height(CommonConstants.FULL_PERCENT) .width(CommonConstants.FULL_PERCENT) .bindSheet($$this.isABSheet, this.ABLoopSheet(), { height:this.isLandscape?'80%': '43%', dragBar: true, showClose: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, blurStyle:BlurStyle.Thin, backgroundColor:Color.Transparent, title: { title: 'AB循环' } }) Stack() { Row() { Blank() .layoutWeight(1) IndexerView( { indexArray: DEFAULT_INDEX, selectedIndex: this.selectedIndex, textSize: 12, selectedColor: "#ff419ee5", normalColor: "#ffa0a0a0", floatSelectedTextColor: "#ffffff", floatSelectedViewColor: "#ff419ee5", onIndexChanged: (index) => { this.listScroller.scrollToIndex(index) } }) .width(24) .height("50%") }.visibility(Visibility.None) Column() { if (!this.isShowCoverHeader()) { this.tabTitle() } if(this.twoFingerType==4){ this.getWaterView() }else{ if(this.isGridMusic){ this.getGridView() }else { this.getListView() } } if (this.modeType == 0) { Column() { Button('同步数据', { type: ButtonType.Capsule, stateEffect: true }) .width(130) .height(50) .margin({ top: 100, bottom: 40 }) .backgroundColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .onClick(() => { this.asyncCurrentPathData() }) } .height(100) .position({ top: 260 }) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }) .animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) .justifyContent(FlexAlign.Center) .opacity(this.isZero ? 1 : 0) .visibility(this.isZero && !this.isFavMusic && !this.isHistory && this.modeType === 0 ? Visibility.Visible : Visibility.None) .animation({ duration: 500, curve: 'ease-in-out' // 可选动画曲线 }) .width('100%') } } .layoutWeight(1) .height('100%') } .layoutWeight(1) .height('100%') Row() { Button(this.isAllSelected ? '全不选' : '全选', { type: ButtonType.Circle, stateEffect: true }) .width(50) .height(50) .fontSize(13) .margin({ top: 10, bottom: 10, right: 6 }) .backgroundColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .onClick(() => { if (this.isAllSelected) { // 全不选 this.selectedFiles = []; } else { // 全选 this.selectedFiles = [...this.getCurFileList()]; } this.isAllSelected = !this.isAllSelected; }) Button('内嵌标签', { type: ButtonType.Capsule, stateEffect: true }) .width(90) .height(50) .fontSize(13) .margin({ top: 10, bottom: 10, right: 6 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .backgroundColor(this.themeColor) .bindContentCover($$this.isOneKeyTags, this.TagsContentCoverBuilder(), { transition: TransitionEffect.translate({ x: 500 }).animation({ curve: curves.springMotion(0.6, 0.8) }) }) .onClick(() => { this.isOneKeyTags = !this.isOneKeyTags }) Button('乱码修复', { type: ButtonType.Capsule, stateEffect: true }) .width(90) .height(50) .fontSize(13) .margin({ top: 10, bottom: 10, right: 6 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .backgroundColor(this.themeColor) .bindContentCover($$this.isFixMessy, this.FixMessyViewBuilder(), { transition: TransitionEffect.translate({ x: 500 }).animation({ curve: curves.springMotion(0.6, 0.8) }) }) .onClick(() => { this.isFixMessy = !this.isFixMessy }) // Button('剪切', { type: ButtonType.Circle, stateEffect: true }) // .width('35%') // .height(60) // .id('music_cut') // .margin({ top: 10, bottom: 10, right: 6 }) // .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) // .backgroundColor(this.themeColor) // .onClick(() => { // this.showCutDialogForMultipleFiles(false, 'music_cut') // }) // // Button('移动', { type: ButtonType.Circle, stateEffect: true }) // .width('35%') // .height(60) // .id('music_cut_current') // .visibility(this.isHasDir ? Visibility.Visible : Visibility.None) // .margin({ top: 10, bottom: 10, right: 6 }) // .backgroundColor(this.themeColor) // .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) // .onClick(() => { // // this.showCutDialogForMultipleFiles(true, 'music_cut_current') // }) // Button('复制', { type: ButtonType.Circle, stateEffect: true }) // .width('35%') // .height(60) // .id('music_copy_current')// .visibility(this.isHasDir?Visibility.None:Visibility.None) // .margin({ top: 10, bottom: 10, right: 6 }) // .backgroundColor(this.themeColor) // .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) // .onClick(() => { // this.showCopyDialogForMultipleFiles(false, 'music_copy_current') // }) Button('删除', { type: ButtonType.Circle, stateEffect: true }) .width(50) .height(50) .fontSize(13) .margin({ top: 10, bottom: 10, right: 6 }) .visibility(this.isFavMusic ? Visibility.None : Visibility.Visible) .backgroundColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .onClick(() => { this.showWarnIsDelete() }) Button('取消', { type: ButtonType.Circle, stateEffect: true }) .width(50) .height(50) .fontSize(13) .margin({ top: 10, bottom: 10, right: 6 }) .backgroundColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .onClick(() => { this.selectedFiles = []; this.isAllSelected = false this.isMultiSelect = false }) } .width('100%') .justifyContent(FlexAlign.Center) .visibility(this.isMultiSelect ? Visibility.Visible : Visibility.None) .opacity(this.isMultiSelect ? 1 : 0) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .position(this.isShowCoverHeader() ? { bottom: 78 } : { bottom: this.isPhoneLan() ? 130 : 170 }) // 将 Row 固定在底部 } .width('100%') .height('100%') .alignContent(Alignment.Top) Column() { this.PlayController() } .hitTestBehavior(HitTestMode.Transparent) .position({ bottom: 0 }) // 将 Row 固定在底部 .opacity(this.barBottomOpacity) } .width('100%') .height('100%') .backgroundImage(this.isCustomizeBg ? this.customizeBgPath : $r('app.color.bottom_control_background')) .backgroundImageSize(this.isCoverOpacity() ? { width: '100%' } : { height: '100%' }) .backgroundImagePosition(Alignment.Center) .backdropBlur(this.blurValue) .backgroundBrightness({ rate: this.isCustomizeBg ? 0.1 : 0, lightUpDegree: this.bgBrightness }) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]) } } @Builder TagsContentCoverBuilder() { Scroll() { Column() { TagsContentCover( { selectedFiles:this.selectedFiles, isShowDrawer:this.isOneKeyTags, onTagsResult:(result: boolean)=>{ this.isMultiSelect = false if(result){//如果嵌入成功,更新数据 this.doUpdateData() } } } ) } } .width('100%') .height('100%') } @Builder FixMessyViewBuilder() { Scroll() { Column() { FixMessyView( { selectedFiles:this.selectedFiles, isShowDrawer:this.isFixMessy, onFixResult:(result: boolean)=>{ this.isMultiSelect = false if(result){//如果嵌入成功,更新数据 this.doUpdateData() } } } ) } } .width('100%') .height('100%') } asyncCurrentPathData() { if (this.modeType == 0) { workerInstance.postMessage({ code: 1, data1: this.context, data2: this.currentPath, data3: this.lockPath, data4: PreferencesUtil.getStringSync('COVER_API', ''), data5: PreferencesUtil.getBooleanSync('autoParseMusicName', true), }); } } // 处理文件选择的函数 handleFileSelection(item: VideoItem, checked: boolean) { if (checked) { if (!this.selectedFiles.some(x => x.filePath == item.filePath)) { this.selectedFiles.push(item); } } else { const index = this.selectedFiles.findIndex(x => x.filePath == item.filePath); if (index > -1) { this.selectedFiles.splice(index, 1); } } } private deleteComponentId: number = 0 //多选删除文件 showWarnIsDelete(){ this.getUIContext().getPromptAction().openCustomDialog({ builder: () => { this.deleteComptentBuilder() }, isModal:true, showInSubWindow:false, maskColor: Color.Transparent, dialogTransition: // 设置弹窗内容显示的过渡效果 TransitionEffect.translate({ x: 0, y: 290, z: 0 }) .animation({ duration: 600, curve: Curve.Smooth }), maskTransition: // 设置蒙层显示的过渡效果 TransitionEffect.opacity(0) .animation({ duration: 600, curve: Curve.Smooth }) }).then((dialogId: number) => { this.deleteComponentId = dialogId }) .catch((error: BusinessError) => { console.error(`openCustomDialog error code is ${error.code}, message is ${error.message}`) }) } @Builder deleteComptentBuilder(){ DeleteComptent({ selectedFiles:this.selectedFiles, onCancel:()=>{ this.isMultiSelect = false this.getUIContext().getPromptAction().closeCustomDialog(this.deleteComponentId) }, onDeleteResult:(result: boolean)=>{ this.isMultiSelect = false this.getUIContext().getPromptAction().closeCustomDialog(this.deleteComponentId) if(result){//如果删除成功,更新数据 this.selectedFiles = []; this.isAllSelected = false this.cache.delete(this.currentPath); this.getSortedFiles(this.currentPath, true); } } }) } //多选移动文件 showCutDialogForMultipleFiles(isCurrent: boolean, viewId: string) { Logger.info('onecold showCutDialogForMultipleFiles') let mList: Array = [] let rPath = this.rootPath if (isCurrent) { mList = Utility.getGlobalNameList(this.videoLocalList, CommonConstants.TYPE_IS_DIR); rPath = this.currentPath } else { mList = Utility.getNameList(this.dirList); rPath = this.rootPath } let dataBean = new BubbleBean(); dataBean.data = mList; dataBean.color = this.themeColor dataBean.onItemClick = (i) => { const newDirPath = rPath + '/' + mList[i]; // Logger.info('newDirPath = ' + newDirPath) this.selectedFiles.forEach((item) => { const newPath = newDirPath + '/' + item.fileName; FileUtil.moveFile(item.filePath, newPath, 1).then(async () => { ToastUtil.showToast('移动成功'); this.table.deleteData(item, () => { this.cache.delete(this.currentPath); // 删除原路径缓存 this.cache.delete(newDirPath); // 删除目标路径缓存 this.getSortedFiles(this.currentPath, true); }); // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap, // item.size,item.pixelMapToString,item.artist,item.album,item.fileName) let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库 this.table.insert(newItem, (id: number) => { //加入数据库 }); } }).catch((error: Error) => { console.error(error.message); }); }); this.isMultiSelect = false this.selectedFiles = []; this.isAllSelected = false XPopup.dismissTop(); }; let pup: XPopup if (this.isDarkMode) { pup = XPopup.Builder() .setPopupPosition(PopupPosition.TOP) .atView(viewId) .setBackgroundColor(Color.Black) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } else { pup = XPopup.Builder() .setPopupPosition(PopupPosition.TOP) .atView(viewId) .setBackgroundColor(Color.White) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } } /** * 加载所有歌单 */ async loadAllPlaylists() { try { this.allPlaylists = await this.playlistTable?.queryAllPlaylists()! LogUtil.info('heanup Loaded playlists: ' + this.allPlaylists.length) } catch (error) { LogUtil.error('heaunp Failed to load playlists: ' + error.message) } } /** * 显示添加到歌单对话框 */ async showAddToPlaylistDialog(item: VideoItem) { // 加载最新的歌单列表 await this.loadAllPlaylists() showAddToPlaylistDialog( item, this.allPlaylists, async (playlistId: string) => { // 添加歌曲到歌单 const success = await this.playlistTable?.addSongToPlaylist(playlistId, item.filePath) if (success) { ToastUtil.showToast('已添加到歌单') // 重新加载歌单列表 await this.loadAllPlaylists() } else { ToastUtil.showToast('歌曲已在该歌单中') } }, () => { LogUtil.info('取消添加到歌单') }, () => { // 创建新歌单 showCreatePlaylistDialog( async (name: string, description: string) => { const success = await this.playlistTable?.createPlaylist(name, description) if (success) { ToastUtil.showToast('歌单创建成功') // 重新加载歌单列表并打开添加对话框 await this.loadAllPlaylists() await this.showAddToPlaylistDialog(item) } else { ToastUtil.showToast('歌单创建失败') } } ) } ) } doFav(item: VideoItem) { LogUtil.info('onecold doFav isFav=' + item.isFav) let isFav = 0 if (Utility.getIsFav(this.favList, item)) { isFav = 0 } else { isFav = 1 } this.table.updateIsFavByFilePath(item.filePath, isFav, async (result: boolean) => { if (result) { if (isFav === 1) { ToastUtil.showToast('收藏成功'); } else { ToastUtil.showToast('取消收藏成功'); } this.deleteCache(this.currentPath) this.getFavList(false) if (this.modeType === 0 && !this.isFavMusic) { LogUtil.info('onecold doFav currentPath =' + this.currentPath) this.getSortedFiles(this.currentPath) } workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName }); workerInstance.postMessage({ code: 3, data: this.context }); workerInstance.postMessage({ code: 4, data: this.context }); } }) } //多选复制文件 showCopyDialogForMultipleFiles(isCurrent: boolean, viewId: string) { console.log('onecold showCopyDialogForMultipleFiles'); let mList: Array = [] let rPath = this.rootPath if (isCurrent) { mList = Utility.getGlobalNameList(this.videoLocalList, CommonConstants.TYPE_IS_DIR); rPath = this.currentPath } else { mList = Utility.getNameList(this.dirList); rPath = this.rootPath } let dataBean = new BubbleBean(); dataBean.data = mList; dataBean.isCopy = true dataBean.color = this.themeColor dataBean.onItemClick = (i) => { const newDirPath = rPath + '/' + mList[i]; // Logger.info('newDirPath = ' + newDirPath) this.selectedFiles.forEach((item) => { const newPath = newDirPath + '/' + item.fileName; FileUtil.copyFile(item.filePath, newPath, 0).then(async () => { ToastUtil.showToast('复制成功'); this.cache.delete(newDirPath); // 删除目标路径缓存 this.getSortedFiles(this.currentPath); // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap, // item.size,item.pixelMapToString,item.artist,item.album,item.fileName) let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName); if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库 this.table.insert(newItem, (id: number) => { //加入数据库 }); } }).catch((error: Error) => { console.error(error.message); }); }); this.isMultiSelect = false this.selectedFiles = []; this.isAllSelected = false XPopup.dismissTop(); }; let pup: XPopup if (this.isDarkMode) { pup = XPopup.Builder() .setPopupPosition(PopupPosition.TOP) .atView(viewId) .setBackgroundColor(Color.Black) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } else { pup = XPopup.Builder() .setPopupPosition(PopupPosition.TOP) .atView(viewId) .setBackgroundColor(Color.White) .asBubble(wrapBuilder(cutPopupBuilder), dataBean) .show(); } } @Builder private DirItem(item: VideoItem, index?: number) { Button({ type: ButtonType.Normal, stateEffect: true }) { Row() { Image(this.modeType !== 0 && StrUtil.isNotEmpty(item.pixelMapPath) ? item.pixelMapPath : $r('app.media.music_group')) .height(this.twoFingerType == 3 ? 55 : this.twoFingerType == 2 ? 35 : 30) .width(this.twoFingerType == 3 ? 55 : this.twoFingerType == 2 ? 35 : 30) .fillColor(this.themeColor)// .fontSize(33) .alt($r('app.media.music_group')) .borderRadius('100%') .clip(true) .margin({ left: 20, top: 8, bottom: 8 }) Column() { Text(item.name.startsWith('.') ? item.name.replace(/\./g, '') : item.name) .fontSize(this.twoFingerType == 1 ? 12 : this.twoFingerType == 2 ? 15 : 18) .maxLines(1)// .width('88%') .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .animation({ duration: 555, curve: 'ease-in-out', }) .transition({ type: TransitionType.Insert, opacity: 0, translate: { x: 160 } }) .margin({ left: 10, right: 20 }) .fontColor($r('app.color.text_color')) Row() { Text(`${this.artistMap.get(item.name)?.length ?? 0}首`) .fontSize(this.twoFingerType == 1 ? 11 : this.twoFingerType == 2 ? 12 : 14) .padding({ top: 8 }) .visibility(this.modeType === 2 ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) .margin({ left: 10 }) Text(`${this.albumMap.get(item.name)?.length ?? 0}首`) .fontSize(this.twoFingerType == 1 ? 11 : this.twoFingerType == 2 ? 12 : 14) .padding({ top: 8 }) .visibility(this.modeType === 3 ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) .margin({ left: 10 }) Blank() } } .height('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.arrow_right')) .height(25) .margin({ right: 10 }) .alignSelf(ItemAlign.Center) .id(item.filePath) } } .backgroundColor(Color.Transparent) .height(this.twoFingerType == 3 ? ITEM_HEIGHT_BIG : this.twoFingerType == 2 ? ITEM_HEIGHT : ITEM_HEIGHT_SMALL) .width('100%') .visibility(this.isShowDir(item.name) ? Visibility.Visible : Visibility.None) .opacity(this.opacityItem) // 绑定透明度 } isShowDir(dirName: string) { this.isShowSimi = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_SIMI, false) this.isShowFAV = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_FAV, true) this.isShowHistory = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_HISTORY, true) if (dirName === undefined) { return true } if (dirName === LocalMusic.STR_LOCK_VIDEO) { return this.isShowSimi } if (dirName === LocalMusic.STR_FAC_VIDEO) { return this.isShowFAV } if (dirName === LocalMusic.STR_HISTORY_MUSIC) { return this.isShowHistory } return true } @State opacityItem: number = 1; // 控制透明度的状态变量 @Builder private MusicItem(item: VideoItem, index?: number) { Button({ type: ButtonType.Normal, stateEffect: true }) { Row() { Stack() { Image(StrUtil.isEmpty(item.pixelMapPath) ? $r('app.media.music_red') : item.pixelMapPath) .fillColor(this.themeColor) .height(this.twoFingerType == 3 ? 55 : this.twoFingerType == 2 ? 35 : 30) .width(this.twoFingerType == 3 ? 55 : this.twoFingerType == 2 ? 35 : 30) .alt($r('app.media.music_red')) .borderRadius('100%') .clip(true) .draggable(false) .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿 .autoResize(true) // 重采样,可减少内存占用 .opacity(this.opacityItem)// 绑定透明度 .margin({ left: 20 }) .gesture(LongPressGesture() .onAction(async () => { //如果是专辑和艺术家的首页,不能长按 if((this.modeType===2||this.modeType==3)&&!this.isCanBack) return //如果是我的收藏 最近播放等也不能长按 if(item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ) return this.longItemFilePath = item.filePath this.tempLyricContent = await this.getLyricContent(item) LogUtil.info('长按this.tempLyricContent = '+this.tempLyricContent) }) .onActionEnd((event: GestureEvent) => { })) .onClick(()=>{ this.doPlay(item,index) this.isShowPlay = true }) } Column() { Column() { Text(this.isShowFileName?item.fileName :item.name) .fontSize(this.twoFingerType == 1 ? 12 : this.twoFingerType == 2 ? 15 : 18) .maxLines(1) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .animation({ duration: 555, curve: 'Linear', }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .margin({ left: this.twoFingerType == 3 ? 6 : 8 }) .padding({right:25}) .bindPopup(this.longItemFilePath === item.filePath, { builder: this.MenuBuilder(item,index,item.filePath), placement: Placement.Top, // autoCancel: true, mask: {color:'#33000000'}, // popupColor: Color.Yellow, enableArrow: false,//是否显示箭头 showInSubWindow: false, onStateChange: (e) => { if (!e.isVisible) { this.longItemFilePath = '' this.tempLyricContent = '' } } }) .gesture(LongPressGesture() .onAction(async () => { //如果是专辑和艺术家的首页,不能长按 if((this.modeType===2||this.modeType==3)&&!this.isCanBack) return //如果是我的收藏 最近播放等也不能长按 if(item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ) return this.longItemFilePath = item.filePath this.tempLyricContent = await this.getLyricContent(item) LogUtil.info('长按this.tempLyricContent = '+this.tempLyricContent) }) .onActionEnd((event: GestureEvent) => { }) ) Row() { Column(){ Text(item.md5Str?.includes('Lossless')? Utility.resourceToString(this.context,$r('app.string.lossless')):item.md5Str)//音质 .fontSize(this.twoFingerType == 1 ? 8 : this.twoFingerType == 2 ? 9 : 11) .padding({ top: 3,right:6,left:6,bottom:3 }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .fontWeight(500) .borderRadius(12) .visibility(StrUtil.isEmpty(item.md5Str)?Visibility.None:Visibility.Visible) .margin({ left: this.twoFingerType == 3 ? 6 : 8 }) .backgroundColor('#FFC107') } .padding({ top: 8 }) Text(StrUtil.isEmpty(item.artist) ? item.duration :this.twoFingerType==2? item.artist +' ' + item.duration : item.artist + ' ' + item?.album) .fontSize(this.twoFingerType == 1 ? 11 : this.twoFingerType == 2 ? 12 : 14) .padding({ top: 8 ,right:25}) .visibility(this.isShowFileName?Visibility.None:Visibility.Visible) .maxLines(1) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .margin({ left: this.twoFingerType == 3 ? 6 : 8 }) Blank() Text(this.isShowFileName?item.duration:item.size) .fontSize(this.twoFingerType == 1 ? 11 : this.twoFingerType == 2 ? 12 : 14) .padding({ top: 8 }) .maxLines(1) .visibility(this.isShowFileName?Visibility.Visible:StrUtil.isEmpty(item.artist) ? Visibility.Visible : Visibility.None) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .margin({ left: 10 }) } .visibility(this.twoFingerType==1?Visibility.None:Visibility.Visible) .gesture(LongPressGesture() .onAction(async () => { //如果是专辑和艺术家的首页,不能长按 if((this.modeType===2||this.modeType==3)&&!this.isCanBack) return //如果是我的收藏 最近播放等也不能长按 if(item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ) return this.longItemFilePath = item.filePath this.tempLyricContent = await this.getLyricContent(item) LogUtil.info('长按this.tempLyricContent = '+this.tempLyricContent) }) .onActionEnd((event: GestureEvent) => { }) ) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) } .height('100%') .margin({ left: 5 }) .layoutWeight(1) Column() { Checkbox({ name: 'checkbox' + index }) .select(this.selectedFiles.some(x => x.filePath === item.filePath)) .selectedColor(this.themeColor) .shape(CheckBoxShape.CIRCLE) .opacity(this.isMultiSelect ? 1 : 0) .animation({ duration: 666, curve: 'Smooth' // 可选动画曲线 }) .visibility(this.isMultiSelect ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => this.handleFileSelection(item, checked)) .margin({ left: 20, top: 8, bottom: 8,right:18 }) .width(22) .height(22) ImageAnimator() .images(this.imagesF)// 动画数组 .duration(1000)// 持续 .state(this.animationState)// 动画状态 .fillMode(FillMode.Forwards) .width(18) .margin({ right: 20, top: 8, bottom: 8 }) .visibility(this.videoUrl === item.filePath ? this.isMultiSelect ? Visibility.None : Visibility.Visible : Visibility.None) .height(18) .iterations(-1) // 播放次数 } } } .backgroundColor(Color.Transparent) .width('100%') .height(this.twoFingerType == 3 ? ITEM_HEIGHT_BIG : this.twoFingerType == 2 ? ITEM_HEIGHT : ITEM_HEIGHT_SMALL) } private readonly tabs: string[] = ['文件夹', '媒体库', '艺术家', '专辑'] @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({ buttons: [{ text: '文件夹' }, { text: '媒体库' },{ text: '艺术家' }], direction: Direction.Ltr, buttonPadding:{top:12,bottom:12}, backgroundColor: Color.Transparent, fontSize:14, selectedFontSize:15, selectedFontColor:Color.White, selectedBackgroundColor: this.themeColor, localizedTextPadding: { end: LengthMetrics.vp(20), start: LengthMetrics.vp(20) } }) @State @Watch('capsuleSelectedIndexesChanged')tabSelectedIndexes: number[] = [0] //当胶囊按钮的选择发生变化时调用此函数 capsuleSelectedIndexesChanged() { this.modeType = this.tabSelectedIndexes[0] } @Builder tabTitle() { Column({ space: 2 }) { SegmentButton({ options: this.tabOptions, selectedIndexes: $tabSelectedIndexes }) Line() .width('100%') .height(1) .backgroundColor($r('app.color.index_background')) .opacity(this.barOpacity) .visibility(this.isCustomizeBg ? Visibility.Hidden : Visibility.Visible) } .width('100%') .visibility(this.isShowTitleBar ? Visibility.Visible : Visibility.None) } // @Builder // tabTitle() { // Column() { // // 使用 Stack 布局来实现绝对定位效果 // Stack() { // // 背景动画容器 // Row() { // ForEach(this.tabs, (i: string, index) => { // Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { // Flex() // .backgroundColor(this.isDarkMode ? $r('app.color.tab_item_bg') : this.themeColor) // .borderRadius(20) // .padding(10) // .margin(6) // .width('100%') // .height('100%') // .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) // .visibility(this.modeType === index ? Visibility.Visible : Visibility.None) // .opacity(this.modeType === index ? 1 : 0) // .animation({ // duration: 666, // playMode: PlayMode.Normal, // curve: 'ease-in-out' // }); // } // .layoutWeight(1) // .margin({ // left: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 20 : 5, // right: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 20 : 5, bottom: 1 // }); // }); // } // .width('100%') // .height(50); // // // 导航栏容器 // Row() { // ForEach(this.tabs, (item: string, index) => { // Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { // Text(item) // .fontSize(16) // .fontColor(this.modeType === index ? Color.White : $r('app.color.text_color')) // .padding(15) // .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) // .onClick(() => { // this.modeType = index; // // this.onModeChange(); // }); // } // .layoutWeight(1) // .stateStyles({ // pressed: { opacity: 0.6 } // 按压反馈 // }); // }); // } // .width('100%') // .height(this.topBarHeight) // .opacity(this.barOpacity) // .backgroundColor(Color.Transparent); // // } // .width('100%') // .height(this.topBarHeight) // .opacity(this.barOpacity) // // Line() // .width('100%') // .height(1) // .backgroundColor($r('app.color.index_background')) // .opacity(this.barOpacity) // .visibility(this.isCustomizeBg ? Visibility.Hidden : Visibility.Visible) // } // .visibility(this.isShowTitleBar ? Visibility.Visible : Visibility.None) // } @State currentTitleName: string = '' //点击歌单,艺术家,专辑进去后的标题 @State currentTitleCover: string | ResourceStr = '' //点击歌单,艺术家,专辑进去后的封面 isShowCoverHeader() { if (!this.isShowHeader) { return false } if (this.modeType == 0) { if (this.isHistory || this.isFavMusic) { return true } if (this.currentPath != this.rootPath) { return true } } return (this.modeType == 2 || this.modeType == 3) && this.isCanBack } @Builder coverHeader() { if (this.isShowCoverHeader()) { Column() { Stack() { // 居中标题 Text('') .fontSize(18) .fontColor(Color.White) .align(Alignment.Center) // 左右按钮 Row() { Image($r('app.media.left_back_white')) .width(28) .height(28) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ left: 12, right: 8 }) .onClick(() => { animateTo({ duration: 555 }, () => { this.doSwipBack() }) }) Blank().flexGrow(1) if (this.modeType == 0 && !this.isHistory && !this.isFavMusic) { Image($r('app.media.add')) .width(25) .height(25) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ left: 8, right: 15 }) .onClick(() => { this.showAddDialog() }) } } .height(48) .width('100%') .alignItems(VerticalAlign.Center) } .height(48) .width('100%') Row() { Column() { Image(this.currentTitleCover) .height(100) .width(100) .alt(this.imageLabel) .borderRadius(8) .clickEffect({ level: ClickEffectLevel.HEAVY }) } .alignItems(HorizontalAlign.Start) .margin({ left: 30, top: 3, bottom: 15 }) Column() { Column() { Text(this.currentTitleName) .fontSize(16) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .fontWeight(FontWeight.Bold) .fontColor($r('app.color.text_color')) .margin({ left: this.currentLyricAlignMode === 0 ? 8 : 0 }) if (this.modeType == 3 && ArrayUtil.isNotEmpty(this.videoLocalList)) { Text(`艺术家:${this.videoLocalList[0].artist}`) .fontSize(14) .padding({ top: 8 }) .maxLines(1) .fontWeight(FontWeight.Bold) .fontColor($r('app.color.text_color')) .margin({ left: this.currentLyricAlignMode === 0 ? 8 : 0 }) if(this.videoLocalList[0].genre){ Text(`风格:${this.videoLocalList[0].genre}`) .fontSize(14) .padding({ top: 8 }) .maxLines(1) .visibility(StrUtil.isEmpty(this.videoLocalList[0].genre)|| this.videoLocalList[0].genre.includes(this.UNKONWN) ?Visibility.None:Visibility.Visible) .fontWeight(FontWeight.Bold) .fontColor($r('app.color.text_color')) .margin({ left: this.currentLyricAlignMode === 0 ? 8 : 0 }) } if(this.videoLocalList[0].year){ Text(`发行时间:${this.videoLocalList[0].year}`) .fontSize(14) .padding({ top: 8 }) .maxLines(1) .visibility(StrUtil.isEmpty(this.videoLocalList[0].year)|| this.videoLocalList[0].year.includes(this.UNKONWN) ?Visibility.None:Visibility.Visible) .fontWeight(FontWeight.Bold) .fontColor($r('app.color.text_color')) .margin({ left: this.currentLyricAlignMode === 0 ? 8 : 0 }) } } if(this.modeType != 3){ Row() { Text(`共${this.videoLocalList.length}首歌`) .fontSize(13) .fontWeight(FontWeight.Bold) .padding({ top: 8 }) .fontColor($r('app.color.text_color')) .margin({ left: this.currentLyricAlignMode === 0 ? 8 : 0 }) Blank() } } } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) } .height('100%') .layoutWeight(1) .margin({ left: 20 }) } this.listViewTitle() } .backgroundImage(this.getCoverTitleBg()) .height(250) .width('100%') .backgroundImageSize({ height: '100%', width: '100%' }) .backgroundBlurStyle(this.isCustomizeBg ? BlurStyle.NONE : BlurStyle.BACKGROUND_ULTRA_THICK) .padding({ top: this.topRectHeight, bottom: this.bottomBarHeight + 48 }) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) .justifyContent(FlexAlign.SpaceBetween) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0.7, y: 0.7 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) } else { this.listViewTitle() } } getCoverTitleBg(): ResourceStr | image.PixelMap { if (this.isCustomizeBg) { return ('rgba(255,255,255,0.2)') } if (this.isDarkMode) { return $r('app.color.user_center_card_background') } return this.currentTitleCover } @Builder listViewTitle() { Column() { Row({ space: 8 }) { Row({ space: 8 }) { Image($r("app.media.hm_playlist")) .width(25) .margin({ left: 8 }) .fillColor(this.themeColor) Text(this.artistList.length ==100 ? `共找到${this.artistCount}位艺术家` :`共找到${this.artistList.length}位艺术家`) .fontColor($r('app.color.text_color')) .fontSize(14) .opacity(this.opacityItem) .visibility(this.modeType === 2 ? Visibility.Visible : Visibility.None) Text(this.albumList.length ==100 ? `共找到${this.albumCount+''}张专辑` :`共找到${this.albumList.length}张专辑`) .fontColor($r('app.color.text_color')) .opacity(this.opacityItem) .visibility(this.modeType === 3 ? Visibility.Visible : Visibility.None) .fontSize(14) } .layoutWeight(1) .visibility(this.isSearchMode ? Visibility.None : Visibility.Visible) Row() { TextInput({ placeholder: this.modeType === 2 ? '输入艺术家名称...' : '输入专辑名称...', text: this.searchText }) .height(35) .onChange((value: string) => { this.onSearchInput(value); }) .layoutWeight(1) Text(`取消`) .margin({ left: 10, right: 10 }) .fontColor($r('app.color.text_color')) .fontSize(16) .onClick(() => { this.isSearchMode = false this.searchText = '' if (this.modeType === 2) { this.updateListData(this.artistList) } else { this.updateListData(this.albumList) } }) } .visibility(this.isSearchMode ? Visibility.Visible : Visibility.None) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) Blank() Image($r("app.media.search")) .width(25) .visibility(this.modeType === 2 || this.modeType === 3 ? Visibility.Visible : Visibility.None) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 })// .opacity(this.opacityItem) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isSearchMode = true }) Image($r("app.media.top_rank")) .width(25)// .opacity(this.opacityItem) .fillColor(this.themeColor) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.showRankDialog() }) Image(this.isGridMusic ? $r('app.media.list') : $r("app.media.grid")) .width(25) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isGridMusic = !this.isGridMusic }) } .visibility(((this.modeType === 2 || this.modeType === 3) && !this.isCanBack) ? Visibility.Visible : Visibility.None) .width('100%') .padding(10) .height(50) .border({ width: { bottom: 1 }, color: this.isCustomizeBg ? Color.Transparent : '#12ec5c87' }) Row({ space: 8 }) { Row({ space: 8 }) { Image($r("app.media.hm_play")) .width(25) .margin({ left: 8 }) .fillColor(this.themeColor) Text(`播放全部`) .fontColor($r('app.color.text_color')) .fontSize(14) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) if(Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL).length>0){ Text(this.modeType==1&&Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL).length==100? `(${this.mediaKuCount}首)` :`(${Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL).length}首)`) .fontColor($r('app.color.text_color')) .fontSize(14) .opacity(this.opacityItem) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) } } .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .visibility(this.isSearchMode ? Visibility.None : Visibility.Visible) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) // .backgroundColor(this.isClickedPLayAll ? $r('app.color.button_click') : Color.Transparent) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.isClickedPLayAll = true; } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) { this.isClickedPLayAll = false; } }) .onClick(() => { // ToastUtil.showToast('当前的手机的折叠状态 ='+DisplayUtil.getFoldStatus()) if (this.modeType === 0) { const playList = this.getCurFileList(); const item = Utility.getPlayItem(playList, this.playType); if (item) { // 显式检查undefined this.doPlay(item); } } else { const item = Utility.getPlayItem(this.mediaKuList, this.playType); if (item) { this.doPlay(item); } } }) Row() { TextInput({ placeholder: '输入名称...', text: this.searchText }) .height(35) .onChange((value: string) => { this.onSearchInput(value); }) .layoutWeight(1) Text(`取消`) .margin({ left: 10, right: 10 }) .fontColor($r('app.color.text_color')) .fontSize(16) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isSearchMode = false this.searchText = '' if(this.modeType==0){ this.updateListData(this.videoLocalList) } else if(this.modeType==1){ this.updateListData(this.mediaKuList) } else if(this.modeType==2&&!this.isCanBack){ this.updateListData(this.artistList) } else if(this.modeType==3&&!this.isCanBack){ this.updateListData(this.albumList) } }) } .visibility(this.isSearchMode ? Visibility.Visible : Visibility.None) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) Blank() Image($r("app.media.search")) .width(25)// .visibility(this.modeType===1?Visibility.Visible:Visibility.None) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 })// .opacity(this.opacityItem) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isSearchMode = true }) Image($r("app.media.refresh")) .fillColor(this.themeColor) .width(25) .visibility(this.isHistory || this.isCanBack || this.isSearchMode ? Visibility.None : Visibility.Visible) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { if (this.isFavMusic) { this.getFavList(true) } else { if(this.modeType == 0){ this.asyncCurrentPathData() }else if(this.modeType ==1){ workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName }); } } }) Image($r("app.media.top_rank")) .width(25) .visibility(this.isHistory || this.isSearchMode ? Visibility.None : Visibility.Visible)// .opacity(this.opacityItem) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.showRankDialog() }) Image(this.isMultiSelect ? $r("app.media.cancel_multi") : $r("app.media.top_flower")) .width(24) .visibility(this.isHistory || this.isSearchMode ? Visibility.None : Visibility.Visible) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isMultiSelect = !this.isMultiSelect }) SymbolGlyph($r('sys.symbol.trash')) .fontColor([this.themeColor]) .fontSize(25) .effectStrategy(1) .visibility(this.isHistory ? Visibility.Visible : Visibility.None) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.clearVideoHistory() //清空播放记录 }) Image(this.isGridMusic ? $r('app.media.list') : $r("app.media.grid")) .width(24) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .bindPopup($$this.tipPopup, { builder: this.popupBuilder, placement: Placement.Bottom, maskColor: 0x33000000, enableArrow: true, onStateChange: (e) => { if (!e.isVisible) { this.tipPopup = false; } } }) .onClick(() => { if(this.twoFingerType==4){ this.isGridMusic = false this.twoFingerType=3 }else{ this.isGridMusic = !this.isGridMusic } PreferencesUtil.put(SettingPage.TWO_FINGER_TYPE, this.twoFingerType) PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) }) } .width('100%') .padding(10) .height(50) .visibility(this.modeType === 0 || this.modeType === 1 || this.isCanBack ? Visibility.Visible : Visibility.None) .border({ width: { bottom: 1 }, color: this.isCustomizeBg ? Color.Transparent : '#12ec5c87' }) } .visibility(this.isShowAllBar ? Visibility.Visible : Visibility.None) } // 第二步:popup构造器定义弹框内容 @Builder popupBuilder() { Column({ space: 2 }) { Text('友情提示:双指缩放可以放大缩小列表') .fontSize(12) .fontWeight(FontWeight.Regular) .fontColor($r('app.color.text_color')) } .justifyContent(FlexAlign.SpaceAround) .width(220) .height(55) .padding(15) } //搜索功能的实现 @State searchText: string = ''; // 用户输入内容 @State filteredList: Array = []; // 过滤后的结果 @State isSearchMode: boolean = false // 实时搜索逻辑(带防抖) private onSearchInput(value: string) { this.searchText = value.trim(); let mSearchList: Array = [] switch (this.modeType) { case 0: // mSearchList = this.videoLocalList // break; case 1: mSearchList = this.mediaKuList break; case 2: mSearchList = this.artistList break; case 3: mSearchList = this.albumList break; } // 新增条件判断:空输入时显示所有数据 if (this.searchText === '') { this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新 } else { this.filteredList = mSearchList.filter((item: VideoItem) => { //支持模糊匹配和艺术家 专辑匹配 const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i'); return regex.test(item.name.toLowerCase())|| regex.test(item.fileName?.toLowerCase() ?? "") || regex.test(item.artist?.toLowerCase() ?? "") || regex.test(item.album?.toLowerCase() ?? "") }); } this.updateListData(this.filteredList); // this.isSearchMode = false; // 根据业务需求决定是否启用 } @State dragItem: number = -1 @State scaleItem: number = -1 @State neighborItem: number = -1 @State neighborScale: number = -1 @State offsetY: number = 0 private dragRefOffset: number = 0 private ITEM_INTV: number = 78 scaleSelect(item: number): number { if (this.scaleItem === item) { return 1.05 } else if (this.neighborItem === item) { return this.neighborScale } else { return 1 } } itemMove(index: number, newIndex: number): void { if (newIndex < 0 || newIndex >= this.videoLocalList.length) { return; } let tmp = this.videoLocalList.splice(index, 1); this.videoLocalList.splice(newIndex, 0, tmp[0]); this.dataSource.pushArrayData(this.videoLocalList) } //瀑布流布局 支持双指缩放 @State columns: number = 2; @State waterFlowScale: number = 1; @State imageScale: number = 1; @State waterFlowOpacity: number = 1; @State waterFlowSnapshot: image.PixelMap | undefined = undefined; private columnChanged: boolean = false; private oldColumn: number = this.columns; private pinchTime: number = 0; // 根据缩放阈值改变列数,触发WaterFlow重新布局 changeColumns(scale: number) { if (scale > (this.columns / (this.columns - 0.5)) && this.columns > 1) { this.columns--; this.columnChanged = true; } else if (scale < 1 && this.columns < 4) { this.columns++; this.columnChanged = true; } } @Builder getWaterView(){ Scroll(this.scroller) { Column() { if(this.isShowCoverHeader()){ this.coverHeader() }else { this.listViewTitle() } WaterFlow({ scroller:this.waterScroller, layoutMode: WaterFlowLayoutMode.SLIDING_WINDOW }) { LazyForEach(this.dataSource, (item: VideoItem, index: number) => { FlowItem() { this.MusicWaterCardItem(item,index) } .width('100%') .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.9 }) .bindContextMenu(this.MenuBuilder(item, index, item.filePath), ResponseType.LongPress, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .bindContextMenu(this.MenuBuilder(item, index, item.filePath), ResponseType.RightClick, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .transition(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 })) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) .gesture( GestureGroup(GestureMode.Exclusive, SwipeGesture({ direction: SwipeDirection.Horizontal }) .onAction((event: GestureEvent) => { if (event) { // this.sideBarController.openSideBar() } }), TapGesture({ count: 1, fingers: 1 }) .onAction(async () => { }) ) ) }, (item: VideoItem) => item.filePath) } // .columnsTemplate('repeat(auto-fit, 160)') .id('waterflow') // 设置id用于截图 .columnsTemplate('1fr '.repeat(this.columns)) // 动态生成列模板,如:'1fr 1fr 1fr'表示3列等宽 .columnsGap(12) // 列间距 .rowsGap(16) // 行间距 .cachedCount(6) .margin( this.isHiCar()?{bottom:this.bottomBarHeight+90}: {bottom:this.isShowCoverHeader()? this.topBarHeight+35: this.isCoverOpacity()?this.topBarHeight+this.bottomBarHeight+78 :this.topBarHeight+this.bottomBarHeight+108}) .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 500, curve: Curve.Ease })) .scrollBar(BarState.Off) // .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(38) }) // 效果:只有向上或者向下滑动到顶部时才会有弹簧回弹效果 .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true }) .width('100%') .height('100%') .padding(10) .opacity(this.waterFlowOpacity) .scale({ x: this.waterFlowScale, y: this.waterFlowScale, centerX: 0, centerY: 0 }) .onAppear(() => { this.scroller.scrollEdge(Edge.Top) }) .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST }) .priorityGesture( PinchGesture() .onActionStart((event: GestureEvent) => { // 双指捏合手势识别成功时截图 this.pinchTime = event.timestamp; this.columnChanged = false; this.oldColumn = this.columns; this.getUIContext().getComponentSnapshot().get('waterflow', (error: Error, pixmap: image.PixelMap) => { if (error) { console.info('error:' + JSON.stringify(error)); return; } this.waterFlowSnapshot = pixmap; if ((this.oldColumn === 1 ) && event.scale > 1|| (this.oldColumn === 4 && event.scale < 1)) { console.info("onecold onActionStart oldColumn:" + this.oldColumn) if (this.oldColumn ===4) { this.twoFingerType =3 this.isGridMusic = true }else if (this.oldColumn ===1) { this.twoFingerType =1 this.isGridMusic = false } return; } }) }) .onActionUpdate((event: GestureEvent) => { // 手势更新:处理缩放逻辑和视觉效果 // 边界限制:防止超出列数范围时继续缩放 if ((this.oldColumn === 1 && event.scale > 1) || (this.oldColumn === 4 && event.scale < 1)) { return; } // 节流处理:避免过于频繁的更新,提升性能 if (event.timestamp - this.pinchTime < 10000000) { return; } this.pinchTime = event.timestamp; this.waterFlowScale = event.scale; this.imageScale = event.scale; // 根据缩放比例设置WaterFlow透明度 this.waterFlowOpacity = (this.waterFlowScale > 1) ? (this.waterFlowScale - 1) : (1 - this.waterFlowScale); this.waterFlowOpacity *= 3; if (!this.columnChanged) { this.changeColumns(event.scale); } // 列数改变后的缩放比例调整:避免出现空白区域 if (this.columnChanged) { this.waterFlowScale = this.imageScale * this.columns / this.oldColumn; // 限制缩放范围,确保视觉效果自然 if (event.scale < 1) { this.waterFlowScale = this.waterFlowScale > 1 ? this.waterFlowScale : 1; } else { this.waterFlowScale = this.waterFlowScale < 1 ? this.waterFlowScale : 1; } } }) .onActionEnd((event: GestureEvent) => { // 手势结束:执行归位动画并保存状态 // 执行归位动画:平滑过渡到正常状态 this.getUIContext()?.animateTo({ duration: 300 }, () => { this.waterFlowScale = 1; this.waterFlowOpacity = 1; }) // 持久化保存当前列数:下次启动时恢复 PreferencesUtil.putSync('lastColumns', this.columns); }) ) //允许拖拽音乐和视频到List或Grid上自动导入视频 .allowDrop([uniformTypeDescriptor.UniformDataType.AUDIO,uniformTypeDescriptor.UniformDataType.VIDEO]) .onDrop((event?: DragEvent) => { try { let dragData: UnifiedData = (event as DragEvent).getData() as UnifiedData; if (dragData !== undefined) { let records: unifiedDataChannel.UnifiedRecord[] = dragData.getRecords(); if (records.length > 0) { for (let i = 0; i < records.length; i++) { let types = records[i].getTypes(); if (types.includes(uniformTypeDescriptor.UniformDataType.FILE_URI)) { const fileUriUds = records[i].getEntry(uniformTypeDescriptor.UniformDataType.FILE_URI) as uniformDataStruct.FileUri; let typeDescriptor = uniformTypeDescriptor.getTypeDescriptor(fileUriUds.fileType); if (typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.AUDIO) ||typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.VIDEO)) { this.targetFile = fileUriUds.oriUri; this.saveVideoDatas([this.targetFile]) hilog.info(0x0000, 'Heanup', '当前targetFile:' + this.targetFile); } } } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData arr is null`); } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData is undefined`); } } catch (error) { const err = error as BusinessError; hilog.error(0x0000, TAG, `startDataLoading errorCode: ${err.code}, errorMessage: ${err.message}`); } }) } } .scrollBar(BarState.Off) } //瀑布流卡片布局 @Builder private MusicWaterCardItem(item: VideoItem, index: number) { Button({ type: ButtonType.Normal, stateEffect: false }) { Stack() { Column() { Image(StrUtil.isEmpty(item.pixelMapPath) ? Utility.getMusisBg2(index) : item.pixelMapPath) .backgroundImageSize(ImageSize.Auto) .borderRadius({ topLeft: 12, topRight : 12, bottomLeft: 0, bottomRight: 0 }) .draggable(false)// 禁止长按手势拖动 // .autoResize(true) // 重采样,可减少内存占用 // .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿 .width('100%') .height('auto') .objectFit(ImageFit.Auto) .bindSheet(this.longItemFilePath== item.filePath, this.editSheet(item), { height: '99%' , dragBar: true, onDisappear: () => { this.longItemFilePath = ''; this.tempLyricContent = '' }, showClose: true, preferType: SheetType.CENTER , title: { title: '编辑标签' } }) .draggable(false) .opacity(this.opacityItem)// 绑定透明度 // .transition(TransitionEffect.move(TransitionEdge.BOTTOM) // .animation({ duration: 500, curve: Curve.Ease })) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .margin({ left: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20, right: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20 }) Column() { Text(item.name.startsWith('.') ? item.name.replace(/\./g, '') : item.name) .fontSize(this.columns == 4 ||this.columns==3? 12 : this.columns == 2 ? 15 : 18) .maxLines(1) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .animation({ duration: 555, curve: 'Linear', }) .bindSheet(this.longItemFilePathDetail== item.filePath, this.detailSheet(item), { height: '95%', dragBar: true, showClose: true, onDisappear: () => { this.longItemFilePathDetail = ''; }, blurStyle:BlurStyle.Thin, backgroundColor:Color.Transparent, preferType: SheetType.CENTER , title: { title: '详情' } }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) Column() { Text(`${this.artistMap.get(item.name)?.length ?? 0}首`) .fontSize(11) .fontSize(this.columns == 4 ||this.columns==3? 10 : this.columns == 2 ? 12 : 14) .visibility(this.modeType === 2 && !this.isCanBack ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) Text(`${this.albumMap.get(item.name)?.length ?? 0}首`) .fontSize(11) .fontSize(this.columns == 4 ||this.columns==3? 10 : this.columns == 2 ? 12 : 14) .visibility(this.modeType === 3 && !this.isCanBack ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) Row(){ Column(){ Text(item.md5Str?.includes('Lossless')? Utility.resourceToString(this.context,$r('app.string.lossless')):item.md5Str)//音质 .fontSize(this.columns == 4 ||this.columns==3? 8 : this.columns == 2 ? 9 : 11) .padding({ top: 3,right:6,left:6,bottom:3 }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .fontWeight(500) .borderRadius(12) .margin({ left: this.twoFingerType == 3 ? 6 : 8 }) .backgroundColor('#FFC107') .visibility(StrUtil.isEmpty(item.md5Str)||this.twoFingerType==1?Visibility.None:Visibility.Visible) } .margin({ top: 2 ,right:6}) .visibility((this.modeType == 3 && !this.isCanBack)||(this.modeType == 2 && !this.isCanBack) ||(this.modeType == 0&&item.type==CommonConstants.TYPE_IS_DIR)? Visibility.None : Visibility.Visible) Text(StrUtil.isEmpty(item.artist) ? item.duration: item.artist) .fontSize(this.columns == 4 ||this.columns==3? 10 : this.columns == 2 ? 12 : 13) .maxLines(1) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .margin({ top: 2}) .visibility(item.type == CommonConstants.TYPE_IS_ARTIST || item.type == CommonConstants.TYPE_IS_ALBUM || item.type == CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .fontWeight(FontWeight.Medium) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) } } .alignItems(HorizontalAlign.Center) // 关键:使内容水平居中 } .height(this.columns == 4 ||this.columns==3? item.type==CommonConstants.TYPE_IS_DIR?30:40 : this.columns == 2 ? (item.type==CommonConstants.TYPE_IS_DIR?38:48) : (item.type==CommonConstants.TYPE_IS_DIR?48:58)) .width('100%') .justifyContent(FlexAlign.Center) .margin({ left: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20, right: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20 }) } .width('100%') .borderRadius(12) .backgroundImage(StrUtil.isEmpty(item.pixelMapPath)?Utility.getMusisBg2(index):item.pixelMapPath) .backgroundImageSize({ height: '100%', width: '100%' }) .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK) .animation({ curve: Curve.Sharp, duration: 300 }) Checkbox({ name: 'checkbox' + index }) .select(this.selectedFiles.some(x => x.filePath == item.filePath)) .selectedColor(this.themeColor) .shape(CheckBoxShape.CIRCLE) .opacity(this.isMultiSelect ? 1 : 0) .animation({ duration: 666, curve: 'Smooth' // 可选动画曲线 }) .visibility(this.isMultiSelect && item.type !== CommonConstants.TYPE_IS_DIR ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => this.handleFileSelection(item, checked)) .margin({ left: 20, top: 8, bottom: 8 }) .width(38) .height(38) } } .backgroundColor(Color.Transparent) .width('100%') .height('auto') .padding({top:2}) .reuseId('card_item') .onClick(() => { if (this.isMultiSelect && item.type !== CommonConstants.TYPE_IS_DIR) { const isChecked = this.selectedFiles.some(x => x.filePath == item.filePath); this.handleFileSelection(item, !isChecked); } else { this.doPlay(item, index) } }) } // Grid布局的开始 private dragRefOffSetX: number = 0; private dragRefOffSetY: number = 0; private FIX_VP_X: number = 180; private FIX_VP_Y: number = 84; isDraggable(index: number): boolean { return index >= 0; } getRowEnd(item: number) { if (item.toString() === '0') { return 1; } else { return 0; } } isInLeft(index: number) { return index % 2 == 0; } down(index: number): void { if ([this.videoLocalList.length - 1, this.videoLocalList.length - 2].includes(index)) { return; } this.offsetY -= this.FIX_VP_Y; this.dragRefOffSetY += this.FIX_VP_Y; this.itemMove(index, index + 1); } up(index: number): void { if (!this.isDraggable(index - 2)) { return; } this.offsetY += this.FIX_VP_Y; this.dragRefOffSetY -= this.FIX_VP_Y; this.itemMove(index, index - 1); } left(index: number): void { if (this.isInLeft(index)) { return; } if (!this.isDraggable(index - 1)) { return; } this.offsetX += this.FIX_VP_X; this.dragRefOffSetX -= this.FIX_VP_X; this.itemMove(index, index - 1) } right(index: number): void { if (!this.isInLeft(index)) { return; } if (!this.isDraggable(index + 1)) { return; } this.offsetX -= this.FIX_VP_X; this.dragRefOffSetX += this.FIX_VP_X; this.itemMove(index, index + 1) } private scroller: Scroller = new Scroller(); private waterScroller: Scroller = new Scroller(); @State startIndex: number = 0 @State endIndex: number = 0 @State scaleValue: number = 1 @State pinchValue: number = 1 layoutOptionsForHeader: GridLayoutOptions = { regularSize: [1, 1], // 只支持[1, 1] irregularIndexes: [0], // 索引为0的GridItem占用一行 }; @Builder getGridView() { Grid(this.scroller, this.layoutOptionsForHeader) { GridItem() { if (this.isShowCoverHeader()) { this.coverHeader() } else { this.listViewTitle() } } LazyForEach(this.dataSource, (item: VideoItem, index: number) => { GridItem() { Stack({ alignContent: Alignment.Center }) { this.MusicItemGrid(item, index) } } .bindContextMenu(this.MenuBuilder(item, index, item.filePath), ResponseType.LongPress, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .bindContextMenu(this.MenuBuilder(item, index, item.filePath), ResponseType.RightClick, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.LIGHT }) .scale({ x: this.scaleItem === index ? 1.02 : 1, y: this.scaleItem === index ? 1.02 : 1 }) .zIndex(this.dragItem === index ? 1 : 0) .translate(this.dragItem === index ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 }) }, (item: VideoItem) => item.filePath) } .width(this.isShowHeader ? '100%' : '94%') .height('100%') .editMode(true) .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 500, curve: Curve.Ease })) .margin( this.isHiCar()?{bottom:this.bottomBarHeight+98}: { bottom: this.isCoverOpacity() ? (this.isShowCoverHeader() ? this.topBarHeight+30 : this.topBarHeight+90) : (this.isShowCoverHeader() ? this.topBarHeight+45 : this.topBarHeight+128) }) .reuseId('grid_item') .layoutWeight(1) .scrollBar(BarState.Off) .supportAnimation(true) .cachedCount(this.twoFingerType==1?5:this.twoFingerType==2?4:3) .columnsTemplate( this.twoFingerType == 3 ? 'repeat(auto-fit, 160)' : this.twoFingerType == 2 ? 'repeat(auto-fit, 110)' : 'repeat(auto-fit, 80)' ) .rowsGap(this.isShowDrawer ? 25 : (this.twoFingerType == 3 ? 10 : this.twoFingerType == 2 ? 5 : 1)) .visibility(this.isGridMusic ? Visibility.Visible : Visibility.None) .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 }) .gesture(PinchGesture({ fingers: 2 }) .onActionEnd((event: GestureEvent) => { this.setGridTwoFingers() }) .onActionUpdate(e => { this.scaleValue = this.pinchValue * e.scale }) .onActionStart(e => { })) .enableScrollInteraction(true) .onScrollIndex((start: number, end: number) => { this.startIndex = start this.endIndex = end }) .onScrollFrameBegin((offset: number) => { if (this.currentWidthBreakpoint !== WidthBreakpoint.WIDTH_SM || (this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_MD && this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_SM)) { if (this.isScrollHide) { this.setBarHeightHide2(offset) } else { this.setBarHeightNormal() } } else { this.setBarHeightHide(offset) } return { offsetRemain: offset }; }) .allowDrop([uniformTypeDescriptor.UniformDataType.AUDIO,uniformTypeDescriptor.UniformDataType.VIDEO]) .onDrop((event?: DragEvent) => { try { let dragData: UnifiedData = (event as DragEvent).getData() as UnifiedData; if (dragData !== undefined) { let records: unifiedDataChannel.UnifiedRecord[] = dragData.getRecords(); if (records.length > 0) { for (let i = 0; i < records.length; i++) { let types = records[i].getTypes(); if (types.includes(uniformTypeDescriptor.UniformDataType.FILE_URI)) { const fileUriUds = records[i].getEntry(uniformTypeDescriptor.UniformDataType.FILE_URI) as uniformDataStruct.FileUri; let typeDescriptor = uniformTypeDescriptor.getTypeDescriptor(fileUriUds.fileType); if (typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.AUDIO) ||typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.VIDEO)) { this.targetFile = fileUriUds.oriUri; this.saveVideoDatas([this.targetFile]) hilog.info(0x0000, 'Heanup', '当前targetFile:' + this.targetFile); } } } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData arr is null`); } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData is undefined`); } } catch (error) { const err = error as BusinessError; hilog.error(0x0000, TAG, `startDataLoading errorCode: ${err.code}, errorMessage: ${err.message}`); } }) } @State targetFile: string = ''; setGridTwoFingers() { this.pinchValue = this.scaleValue; Logger.info('this.pinchValue = ' + this.pinchValue); if (this.pinchValue >= 1) { this.twoFingerType++ Logger.info('this.twoFingerType1 = ' + this.twoFingerType); if(this.twoFingerType >= 4){ this.twoFingerType = 4//切换成瀑布流 this.columns = 4 } } else { this.twoFingerType-- Logger.info('this.twoFingerType2 = ' + this.twoFingerType); if (this.twoFingerType < 1) { this.twoFingerType = 3 this.isGridMusic = false PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) } } PreferencesUtil.put(SettingPage.TWO_FINGER_TYPE, this.twoFingerType) PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) this.pinchValue = 1; this.scaleValue = 1; } @State longItemFilePath: string = '' @State tempLyricContent: string = '' @Builder private MusicItemGrid(item: VideoItem, index: number) { Button({ type: ButtonType.Normal, stateEffect: false }) { Stack() { Column() { Image(StrUtil.isEmpty(item.pixelMapPath) ? Utility.getMusisBg2(index) : item.pixelMapPath) .height(this.getGridHeight()) .width(this.getGridWight()) .draggable(false) .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿 .autoResize(true) // 重采样,可减少内存占用 .alt($r('app.media.ic_avatar2')) .clip(true) .borderRadius({ topLeft: 12, topRight: 12, bottomLeft: 0, bottomRight: 0 }) .bindSheet(this.longItemFilePath== item.filePath, this.editSheet(item), { height: '99%' , dragBar: true, onDisappear: () => { this.longItemFilePath = ''; this.tempLyricContent = '' }, showClose: true, preferType: SheetType.CENTER , title: { title: '编辑标签' } }) .draggable(false) .opacity(this.opacityItem)// 绑定透明度 .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .margin({ left: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20, right: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20 }) Column() { Text(this.isShowFileName&&item.type!=CommonConstants.TYPE_IS_DIR?item.fileName :item.name.startsWith('.') ? item.name.replace(/\./g, '') : item.name) .fontSize(this.twoFingerType == 1 ? 12 : this.twoFingerType == 2 ? 15 : 18) .maxLines(1) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .animation({ duration: 555, curve: 'Linear', }) .bindSheet(this.longItemFilePathDetail== item.filePath, this.detailSheet(item), { height: '95%', dragBar: true, showClose: true, onDisappear: () => { this.longItemFilePathDetail = ''; }, blurStyle:BlurStyle.Thin, backgroundColor:Color.Transparent, preferType: SheetType.CENTER , title: { title: '详情' } }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) Column() { Text(`${this.artistMap.get(item.name)?.length ?? 0}首`) .fontSize(11) .fontSize(this.twoFingerType == 1 ? 10 : this.twoFingerType == 2 ? 12 : 14) .visibility(this.modeType === 2 && !this.isCanBack ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) Text(`${this.albumMap.get(item.name)?.length ?? 0}首`) .fontSize(11) .fontSize(this.twoFingerType == 1 ? 10 : this.twoFingerType == 2 ? 12 : 14) .visibility(this.modeType === 3 && !this.isCanBack ? Visibility.Visible : Visibility.None) .fontColor($r('app.color.text_color')) Row(){ Column(){ Text(item.md5Str?.includes('Lossless')? Utility.resourceToString(this.context,$r('app.string.lossless')):item.md5Str)//音质 .fontSize(this.twoFingerType == 1 ? 8 : this.twoFingerType == 2 ? 9 : 11) .padding({ top: 3,right:6,left:6,bottom:3 }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .fontWeight(500) .borderRadius(12) .margin({ left: this.twoFingerType == 3 ? 6 : 8 }) .backgroundColor('#FFC107') .visibility(StrUtil.isEmpty(item.md5Str)||this.twoFingerType==1?Visibility.None:Visibility.Visible) } .margin({ top: 2 ,right:6}) .visibility((this.modeType == 3 && !this.isCanBack)||(this.modeType == 2 && !this.isCanBack) ||(this.modeType == 0&&item.type==CommonConstants.TYPE_IS_DIR)? Visibility.None : Visibility.Visible) Text(this.isShowFileName?item.duration:StrUtil.isEmpty(item.artist) ? item.duration: item.artist) .fontSize(this.twoFingerType == 1 ? 10 : this.twoFingerType == 2 ? 12 : 13) .maxLines(1) .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动 .margin({ top: 2}) .visibility(item.type == CommonConstants.TYPE_IS_ARTIST || item.type == CommonConstants.TYPE_IS_ALBUM || item.type == CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .fontWeight(FontWeight.Medium) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) } } .alignItems(HorizontalAlign.Center) // 关键:使内容水平居中 } .height(this.twoFingerType == 3 ? 60 : this.twoFingerType == 2 ? 48 : 35) .width(this.getGridWight()) .justifyContent(FlexAlign.Center) .margin({ left: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20, right: this.currentBreakpoint !== BreakpointTypeEnum.SM ? 40 : 20 }) } .width(this.getGridWight()) .borderRadius(12) .backgroundImage(StrUtil.isEmpty(item.pixelMapPath)?Utility.getMusisBg2(index):item.pixelMapPath) .backgroundImageSize({ height: '100%', width: '100%' }) .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK) .animation({ curve: Curve.Sharp, duration: 300 }) Checkbox({ name: 'checkbox' + index }) .select(this.selectedFiles.some(x => x.filePath == item.filePath)) .selectedColor(this.themeColor) .shape(CheckBoxShape.CIRCLE) .opacity(this.isMultiSelect ? 1 : 0) .animation({ duration: 666, curve: 'Smooth' // 可选动画曲线 }) .visibility(this.isMultiSelect && item.type !== CommonConstants.TYPE_IS_DIR ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => this.handleFileSelection(item, checked)) .margin({ left: 20, top: 8, bottom: 8 }) .width(38) .height(38) } } .backgroundColor(Color.Transparent) .width('100%') .padding({ top: 15 }) .height(this.getGridAllHeight()) .onClick(() => { if (this.isMultiSelect && item.type !== CommonConstants.TYPE_IS_DIR) { const isChecked = this.selectedFiles.some(x => x.filePath == item.filePath); this.handleFileSelection(item, !isChecked); } else { this.doPlay(item, index) } }) } getGridAllHeight() { let heightG = 222 switch (this.twoFingerType) { case 1: heightG = 143 break; case 2: heightG = 178 break; case 3: heightG = 222 break; } return heightG } getGridHeight() { let heightG = 150 switch (this.twoFingerType) { case 1: heightG = 80 break; case 2: heightG = 110 break; case 3: heightG = 150 break; } return heightG } getGridWight() { let wightG = 158 switch (this.twoFingerType) { case 1: wightG = 80 break; case 2: wightG = 110 break; case 3: wightG = 158 break; } return wightG } @State isShowDetail:boolean = false @State longItemFilePathDetail:string ='' @Builder MenuBuilder(item: VideoItem, index: number, filePath: string) { if( ((this.modeType===2||this.modeType==3)&&!this.isCanBack) ||(item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC) ){ //如果是专辑和艺术家的首页,不能长按 //如果是我的收藏 最近播放等也不能长按 }else{ Scroll() { Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { Menu() { MenuItem({ symbolStartIcon: new SymbolGlyphModifier(Utility.getIsFav(this.favList, item) ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart')), content: Utility.getIsFav(this.favList, item) ? '取消收藏' : '收藏' }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { if (item) { this.doFav(item) } this.longItemFilePath = '' }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.forward_end_fill')), content: '下一首播放' }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { this.addToNextPlay(item); this.longItemFilePath = '' }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.music_note_list')), content: '添加到歌单' }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(async () => { this.longItemFilePath = '' await this.showAddToPlaylistDialog(item) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')), content: '编辑标签' }) .bindSheet($$this.isShowEdit, this.editSheet(item), { height: '99%', dragBar: true, showClose: true, preferType: SheetType.CENTER , title: { title: '编辑标签' } }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(async() => { this.setEditStrEmpty() this.tempLyricContent = await this.getLyricContent(item); if(this.isGridMusic||this.twoFingerType==4){ this.longItemFilePath = item.filePath }else{ this.isShowEdit = !this.isShowEdit; } }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.checkmark_square_on_square')), content: $r('app.string.select_all') }) .onClick(() => { this.isMultiSelect = !this.isMultiSelect this.longItemFilePath = '' this.selectedFiles.push(item) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.rename')), content: $r('app.string.rename') }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? (item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ? Visibility.None : Visibility.Visible) : Visibility.Visible) .onClick(() => { this.showReNameDialog(item, index + '', filePath) this.longItemFilePath = '' }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')), content: $r('app.string.delete') }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? (item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ? Visibility.None : Visibility.Visible) : Visibility.Visible) .onClick(() => { this.selectedFiles.push(item) this.showWarnIsDelete() this.longItemFilePath = '' }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.info_circle')), content: $r('app.string.detail') }) .bindSheet($$this.isShowDetail, this.detailSheet(item), { height:'95%', dragBar: true, showClose: true, blurStyle:BlurStyle.Thin, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, backgroundColor:Color.Transparent, title: { title: '详情信息' } }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { if(this.isGridMusic||this.twoFingerType==4){ this.longItemFilePathDetail = item.filePath }else{ this.isShowDetail = !this.isShowDetail } }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.share')), content: $r('app.string.share') }) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { Utility.doShareMusic(item, getContext(this) as common.UIAbilityContext) this.longItemFilePath = '' }) } .font({ size: 15, weight: FontWeight.Normal }) } .width(180) } .height('auto') } } private listMaxScrollOffsetY: number = 0 @State selectedIndex: number = -1 setlistTwoFingers() { this.pinchValue = this.scaleValue; Logger.info('this.pinchValue = ' + this.pinchValue); if (this.pinchValue >= 1) { this.twoFingerType++ Logger.info('this.twoFingerType1 = ' + this.twoFingerType); if (this.twoFingerType > 3) { this.twoFingerType = 1 this.isGridMusic = true PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) } } else { this.twoFingerType-- Logger.info('this.twoFingerType2 = ' + this.twoFingerType); if (this.twoFingerType < 1) { this.twoFingerType = 4 this.columns = 1 } } PreferencesUtil.put(SettingPage.TWO_FINGER_TYPE, this.twoFingerType) PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) this.pinchValue = 1; this.scaleValue = 1; } @Builder getListView() { List({ scroller: this.listScroller }) { ListItemGroup({ header: this.coverHeader() }) { LazyForEach(this.dataSource, (item: VideoItem, index: number) => { ListItem() { Column() { if (item.type === CommonConstants.TYPE_IS_DIR || item.type === CommonConstants.TYPE_IS_ARTIST || item.type === CommonConstants.TYPE_IS_ALBUM) { this.DirItem(item, index) } else if (item.type === CommonConstants.TYPE_IS_CSJAD) { //如果是穿山甲广告 } else { this.MusicItem(item, index) } } .shadow(this.scaleItem === index ? { radius: 70, color: '#15000000', offsetX: 0, offsetY: 0 } : { radius: 0, color: '#15000000', offsetX: 0, offsetY: 0 }) .animation({ curve: Curve.Sharp, duration: 300 }) } // .transition(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 })) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }) .animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.LIGHT }) .swipeAction(( (this.isSwipe&&this.modeType == 0 || this.modeType == 1 || ((this.modeType == 3 || this.modeType == 2) && this.isCanBack)) && item.type !== CommonConstants.TYPE_IS_CSJAD && !this.isHistory) ? { end: this.DeleteButton(item, index, item.filePath) } : {}) //左滑 .onClick(() => { if (this.isMultiSelect && item.type !== CommonConstants.TYPE_IS_DIR) { const isChecked = this.selectedFiles.some(x => x.filePath == item.filePath); this.handleFileSelection(item, !isChecked); } else { this.doPlay(item, index) } }) //拖动List关键代码开始 .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) }) .zIndex(this.dragItem === index ? 1 : 0) .translate(this.dragItem === index ? { y: this.offsetY } : { y: 0 }) .gesture( GestureGroup(GestureMode.Sequence, LongPressGesture({ repeat: true }) .onAction((event?: GestureEvent) => { animateTo({ curve: Curve.Friction, duration: 300 }, () => { this.scaleItem = index }) }) .onActionEnd(() => { animateTo({ curve: Curve.Friction, duration: 300 }, () => { this.scaleItem = -1 }) }), PanGesture({ fingers: 1, direction: null, distance: 0 }) .onActionStart(() => { this.dragItem = index // 记录当前拖动项索引 this.dragRefOffset = 0 // 重置偏移基准 }) .onActionUpdate((event: GestureEvent) => { this.offsetY = event.offsetY - this.dragRefOffset; this.neighborItem = -1; let indexA = this.videoLocalList.indexOf(item); // LogUtil.info('onecold indexA = '+indexA +' index=='+index) let curveValue: curves.ICurve = curves.initCurve(Curve.Sharp); let value: number = 0; if (this.offsetY < 0) { value = curveValue.interpolate(-this.offsetY / this.ITEM_INTV); this.neighborItem = indexA - 1; this.neighborScale = 1 - value / 20; } else if (this.offsetY > 0) { value = curveValue.interpolate(this.offsetY / this.ITEM_INTV); this.neighborItem = indexA + 1; this.neighborScale = 1 - value / 20; } if (this.offsetY > this.ITEM_INTV / 2 && indexA + 1 < this.videoLocalList.length) { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.offsetY -= this.ITEM_INTV; this.dragRefOffset += this.ITEM_INTV; this.itemMove(indexA, indexA + 1); }); } else if (this.offsetY < -this.ITEM_INTV / 2 && indexA - 1 >= 0) { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.offsetY += this.ITEM_INTV; this.dragRefOffset -= this.ITEM_INTV; this.itemMove(indexA, indexA - 1); }); } let curListOffset = this.listScroller.currentOffset() // 获取手指信息 let fingerInfo = event.fingerList[0] let clickPercentY = (fingerInfo.globalY - Number(this.listArea.globalPosition.y)) / Number(this.listArea.height) if (clickPercentY > 0.8 && !this.listScroller.isAtEnd()) { let scrollVelocity = clickPercentY > 0.9 ? 4 : 2 if (this.listMaxScrollOffsetY - curListOffset.yOffset > scrollVelocity + 5) { this.listScroller.scrollTo({ xOffset: 0, yOffset: curListOffset.yOffset += scrollVelocity }) } } else if (clickPercentY < 0.2 && curListOffset.yOffset >= 0) { let scrollVelocity = clickPercentY < 0.1 ? 4 : 2 if (curListOffset.yOffset > scrollVelocity + 5) { this.listScroller.scrollTo({ xOffset: 0, yOffset: curListOffset.yOffset -= scrollVelocity }) } } }) .onActionEnd((event: GestureEvent) => { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.dragItem = -1 this.neighborItem = -1 }) animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => { this.scaleItem = -1 }) }) ) .onCancel(() => { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.dragItem = -1 this.neighborItem = -1 }) animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => { this.scaleItem = -1 }) }) ) //拖动List关键代码结束 }, (item: VideoItem) => item.filePath) } } .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 }) .gesture(PinchGesture({ fingers: 2 }) .onActionEnd((event: GestureEvent) => { this.setlistTwoFingers() }) .onActionUpdate(e => { this.scaleValue = this.pinchValue * e.scale }) .onActionStart(e => { })) // .divider({ strokeWidth: 1, color: this.isDarkMode? '#333333':'#ffe9f0f0' }) .margin( this.isHiCar()&&(this.windowWidth>1800&&this.windowHeight>=1200)?{bottom:this.bottomBarHeight+98}: { bottom: this.isCoverOpacity() ? (this.isShowCoverHeader() ? this.topBarHeight+25 : this.topBarHeight+80) : (this.isShowCoverHeader() ? this.topBarHeight+35 : this.isHiCarSmall()?this.topBarHeight+90:this.topBarHeight+125) }) .cachedCount(6) .reuseId('list_item') .borderRadius(20) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) // .transition(TransitionEffect.move(TransitionEdge.BOTTOM) // .animation({ duration: 500, curve: Curve.Ease })) .layoutWeight(1) .onScrollIndex((start) => { this.selectedIndex = start }) .visibility(this.isGridMusic ? Visibility.None : Visibility.Visible) .lanes( new BreakpointType({ sm: 1, md: 2, lg: 2, xl: 3, xxl: 4 }).getValue(this.currentBreakpoint), new BreakpointType({ sm: 0, md: 12, lg: 12, xl: 16, xxl: 20 }).getValue(this.currentBreakpoint) ) .onAreaChange((oldValue: Area, newValue: Area) => { this.listArea = newValue if (this.twoFingerType == 3) { this.listMaxScrollOffsetY = this.videoLocalList.length * (ITEM_HEIGHT_BIG) - 10 } else if (this.twoFingerType == 2) { this.listMaxScrollOffsetY = this.videoLocalList.length * (ITEM_HEIGHT) - 10 } else if (this.twoFingerType == 1) { this.listMaxScrollOffsetY = this.videoLocalList.length * (ITEM_HEIGHT_SMALL) - 10 } }) .onScrollFrameBegin((offset: number) => { //滚动小屏幕 比如puraX外屏和手机横屏可以隐藏bottomBarHeight topBarHeight // if (this.isPhoneLan()) { // this.setBarHeightHide(offset) // } else if (this.currentWidthBreakpoint !== WidthBreakpoint.WIDTH_SM || (this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_MD && this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_SM)) { if (this.isScrollHide) { this.setBarHeightHide2(offset) } else { this.setBarHeightNormal() } } else { this.setBarHeightHide(offset) } return { offsetRemain: offset }; }) //拖拽pc或者pad的用鼠标拖拽音乐和视频到List或Grid上自动导入视频 .allowDrop([uniformTypeDescriptor.UniformDataType.AUDIO,uniformTypeDescriptor.UniformDataType.VIDEO]) .onDrop((event?: DragEvent) => { try { let dragData: UnifiedData = (event as DragEvent).getData() as UnifiedData; if (dragData !== undefined) { let records: unifiedDataChannel.UnifiedRecord[] = dragData.getRecords(); if (records.length > 0) { for (let i = 0; i < records.length; i++) { let types = records[i].getTypes(); if (types.includes(uniformTypeDescriptor.UniformDataType.FILE_URI)) { const fileUriUds = records[i].getEntry(uniformTypeDescriptor.UniformDataType.FILE_URI) as uniformDataStruct.FileUri; let typeDescriptor = uniformTypeDescriptor.getTypeDescriptor(fileUriUds.fileType); if (typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.AUDIO) ||typeDescriptor.belongsTo(uniformTypeDescriptor.UniformDataType.VIDEO)) { this.targetFile = fileUriUds.oriUri; this.saveVideoDatas([this.targetFile]) hilog.info(0x0000, 'Heanup', '当前targetFile:' + this.targetFile); } } } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData arr is null`); } } else { hilog.info(0x0000, TAG, `%{public}s`, `dragData is undefined`); } } catch (error) { const err = error as BusinessError; hilog.error(0x0000, TAG, `startDataLoading errorCode: ${err.code}, errorMessage: ${err.message}`); } }) } setBarHeightNormal() { this.hideDone = false; this.getUIContext().animateTo({ duration: 300 }, () => { this.bottomBarHeight = 55 + 0; this.topBarHeight = 50 + 0; this.barOpacity = 1; this.barBottomOpacity = 1 this.currentYOffset = 0; this.isHiding = false; }); } setBarHeightHide(offset: number) { if (offset > 0 && !this.hideDone) { this.currentYOffset += offset; if (this.currentYOffset <= 100) { this.bottomBarHeight = this.bottomBarHeight * (1 - this.currentYOffset / 100); this.topBarHeight = this.topBarHeight * (1 - this.currentYOffset / 100); this.barOpacity = 1 - this.currentYOffset / 100; this.barBottomOpacity = 1 - this.currentYOffset / 100; } else { this.topBarHeight = 0; this.bottomBarHeight = 0; this.barBottomOpacity = 0 this.barOpacity = 0; this.hideDone = true; } this.isHiding = true; } if (offset < 0 && this.isHiding) { this.hideDone = false; this.getUIContext().animateTo({ duration: 300 }, () => { this.bottomBarHeight = 55 + 0; this.topBarHeight = 50 + 0; this.barOpacity = 1; this.barBottomOpacity = 1 this.currentYOffset = 0; this.isHiding = false; }); } } //这个方法没隐藏bottomBar,只隐藏topbar setBarHeightHide2(offset: number) { if (offset > 0 && !this.hideDone) { this.currentYOffset += offset; if (this.currentYOffset <= 100) { this.topBarHeight = this.topBarHeight * (1 - this.currentYOffset / 100); this.barOpacity = 1 - this.currentYOffset / 100; } else { this.topBarHeight = 0; this.barOpacity = 0; this.hideDone = true; } this.isHiding = true; } if (offset < 0 && this.isHiding) { this.hideDone = false; this.getUIContext().animateTo({ duration: 300 }, () => { this.topBarHeight = 50 + 0; this.barOpacity = 1; this.currentYOffset = 0; this.isHiding = false; }); } } private async doPlay(item: VideoItem, index?: number, isFromSonPlayList?: boolean,isOpen?:boolean) { Logger.info(`heanup doPlay被调用 - 歌曲: ${item.name}, 文件路径: ${item.filePath}, 索引: ${index}, 类型: ${item.type}, isFromSonPlayList: ${isFromSonPlayList}`) switch (item.type) { case CommonConstants.TYPE_IS_DIR: // 进入歌单前安全保存当前滚动偏移量,支持列表和网格 let offsetB = 0 if(this.twoFingerType==4){ if (this.waterScroller && typeof this.waterScroller.currentOffset === 'function') { let cur = this.waterScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetB = cur.yOffset } } this.mScrollMap.set('lastWaterDirScrollOffset', offsetB) }else if (this.isGridMusic) { if (this.scroller && typeof this.scroller.currentOffset === 'function') { let cur = this.scroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetB = cur.yOffset } } this.mScrollMap.set('lastGridDirScrollOffset', offsetB) } else { if (this.listScroller && typeof this.listScroller.currentOffset === 'function') { let cur = this.listScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetB = cur.yOffset } } this.mScrollMap.set('lastListDirScrollOffset', offsetB) } if (item.name === LocalMusic.STR_LOCK_VIDEO) { this.showVerifyDialog() this.currentTitleName = '私密音频' this.currentTitleCover = $r('app.media.ic_avatar5') } else if (item.name === LocalMusic.STR_HISTORY_MUSIC) { this.titleBarModel.setTitleName('最近播放') this.currentTitleCover = $r('app.media.ic_avatar2') this.currentTitleName = '最近播放' //加载动画效果 this.updateListData(this.historyList, true) // this.videoLocalList = this.historyList if (ArrayUtil.isEmpty(this.historyList)) { ToastUtil.showToast('最近播放记录空空如也') } else { this.currentTitleCover = Utility.getFirstCoverFromList(this.historyList) } this.isHistory = true this.titleBarModel.setLeftIconMain($r('app.media.left_back_white')) } else if (item.name === LocalMusic.STR_FAC_VIDEO) { this.titleBarModel.setTitleName('我的收藏') this.currentTitleName = '我的收藏' this.currentTitleCover = $r('app.media.ic_avatar1') //加载动画效果 this.updateListData(this.favList) if (ArrayUtil.isEmpty(this.favList)) { ToastUtil.showToast('无音乐收藏记录') } else { this.currentTitleCover = Utility.getFirstCoverFromList(this.favList) } this.isFavMusic = true this.titleBarModel.setLeftIconMain($r('app.media.left_back_white')) } else { this.currentTitleName = item.name this.currentPath = item.filePath this.currentTitleCover = $r('app.media.ic_avatar5') this.getSortedFiles(this.currentPath) } //进入歌单的时候的滚动位置在顶部 if(this.twoFingerType==4){ setTimeout(() => { this.waterScroller.scrollEdge(Edge.Top) this.scroller.scrollEdge(Edge.Top) }, 380) }else{ if (this.isGridMusic) { this.scroller.scrollEdge(Edge.Top) }else{ this.listScroller.scrollEdge(Edge.Top) } } break; case CommonConstants.TYPE_IS_ARTIST: // 进入艺术家前安全保存当前滚动偏移量,支持列表和网格 let offsetA = 0 if(this.twoFingerType==4){ if (this.waterScroller && typeof this.waterScroller.currentOffset === 'function') { let cur = this.waterScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetA = cur.yOffset } } this.mScrollMap.set('lastWaterArtistScrollOffset', offsetA) }else if (this.isGridMusic) { if (this.scroller && typeof this.scroller.currentOffset === 'function') { let cur = this.scroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetA = cur.yOffset } } this.mScrollMap.set('lastGridArtistScrollOffset', offsetA) } else { if (this.listScroller && typeof this.listScroller.currentOffset === 'function') { let cur = this.listScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offsetA = cur.yOffset } } this.mScrollMap.set('lastListArtistScrollOffset', offsetA) } let mList = this.artistMap.get(item.name) if (mList !== undefined && ArrayUtil.isNotEmpty(mList)) { Utility.doSortListAscending(mList) this.isCanBack = true this.titleBarModel.setTitleName(item.name) this.currentTitleName = '艺术家:' + item.name if (item.pixelMapPath) { this.currentTitleCover = item.pixelMapPath } this.updateListData(mList) this.titleBarModel.setLeftIconMain($r('app.media.left_back_white')) } //进入歌单的时候的滚动位置在顶部 if(this.twoFingerType==4){ setTimeout(() => { this.waterScroller.scrollEdge(Edge.Top) this.scroller.scrollEdge(Edge.Top) }, 380) }else{ if (this.isGridMusic) { this.scroller.scrollEdge(Edge.Top) } } break; case CommonConstants.TYPE_IS_ALBUM: // 进入专辑前安全保存当前滚动偏移量,支持列表和网格 let offset = 0 if(this.twoFingerType==4){ if (this.waterScroller && typeof this.waterScroller.currentOffset === 'function') { let cur = this.waterScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offset = cur.yOffset } } this.mScrollMap.set('lastWaterAlBumScrollOffset', offset) }else if (this.isGridMusic) { if (this.scroller && typeof this.scroller.currentOffset === 'function') { let cur = this.scroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offset = cur.yOffset } } this.mScrollMap.set('lastGridAlBumScrollOffset', offset) } else { if (this.listScroller && typeof this.listScroller.currentOffset === 'function') { let cur = this.listScroller.currentOffset() if (cur && typeof cur.yOffset === 'number') { offset = cur.yOffset } } this.mScrollMap.set('lastListAlBumScrollOffset', offset) } let albumList = this.albumMap.get(item.name) if (albumList !== undefined && ArrayUtil.isNotEmpty(albumList)) { Utility.doSortListAscending(albumList) this.isCanBack = true this.titleBarModel.setTitleName(item.name) this.currentTitleName = '专辑:' + item.name if (item.pixelMapPath) { this.currentTitleCover = item.pixelMapPath } this.updateListData(albumList) this.titleBarModel.setLeftIconMain($r('app.media.left_back_white')) this.mScrollMap.set(item.name, this.selectedIndex) } //进入歌单的时候的滚动位置在顶部 if(this.twoFingerType==4){ setTimeout(() => { this.waterScroller.scrollEdge(Edge.Top) this.scroller.scrollEdge(Edge.Top) }, 380) }else{ if (this.isGridMusic) { this.scroller.scrollEdge(Edge.Top) } } break; case CommonConstants.TYPE_IS_CSJAD: return break; case CommonConstants.TYPE_LOCAL: if(item&&item.filePath.toLowerCase().endsWith('.cue')){ const cueinfo:CueInfo = await parseCueFile(item.filePath) const cueTracks:CueTrack[] = cueinfo.tracks console.log('onecold cue cueTracks =', JSON.stringify(cueTracks)); if(ArrayUtil.isNotEmpty(cueTracks)){ this.showCueDialog(cueTracks,cueinfo) } // this.showCueSheet = !this.showCueSheet return } if (this.CONTROL_PlayStatus !== PlayStatus.INIT) { this.stop(); } if (isOpen) { this.currentSong = item if (index !== undefined) { this.curIndex = index } this.songList = [] this.songList.push(item) this.sonDataSource.pushArrayData(this.songList) }else if(isFromSonPlayList){ //点击来自右下角的播放列表 this.currentSong = item if (index !== undefined) { this.curIndex = index } // 确保播放列表是完整的歌单歌曲列表(已在finishLoadingPlaylist中设置) Logger.info(`heanup isFromSonPlayList分支 - 当前播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`) }else { let globalVideoList = Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL) as VideoItem[]; this.curIndex = Utility.getCurIndexFromGlobalList(globalVideoList, item.filePath) this.songList = globalVideoList this.sonDataSource.pushArrayData(this.songList) this.currentSong = globalVideoList[this.curIndex] } AppStorage.setOrCreate('currentSong',this.currentSong) ; this.videoUrl = this.currentSong.filePath this.name = this.currentSong.name this.cover = this.currentSong.pixelMapPath this.artist = this.currentSong.artist LogUtil.info('this.currentSong.duration =' + this.currentSong.duration) LogUtil.info('this.currentSongmimeType =' + this.currentSong.mimeType) this.startPlayOrResumePlay() break; } } private cueComponentId: number = 0 //显示cue分轨列表对话框 showCueDialog(cueTracks:CueTrack[],cueinfo:CueInfo){ this.getUIContext().getPromptAction().openCustomDialog({ builder: () => { this.cueComptentBuilder(cueTracks,cueinfo) }, isModal:true, showInSubWindow:false, maskColor: Color.Transparent, dialogTransition: // 设置弹窗内容显示的过渡效果 TransitionEffect.translate({ x: 0, y: 390, z: 0 }) .animation({ duration: 600, curve: Curve.Smooth }), maskTransition: // 设置蒙层显示的过渡效果 TransitionEffect.opacity(0.5) .animation({ duration: 600, curve: Curve.Smooth }) }).then((dialogId: number) => { this.cueComponentId = dialogId }) .catch((error: BusinessError) => { console.error(`openCustomDialog error code is ${error.code}, message is ${error.message}`) }) } @Builder cueComptentBuilder(cueTracks:CueTrack[]=[],cueinfo:CueInfo){ CueComptent({ title:cueinfo.title, cueTracks:cueTracks, isLongNameRoLL:this.isLongNameRoLL, onCancel:()=>{ this.getUIContext().getPromptAction().closeCustomDialog(this.cueComponentId) }, onDoItemClick:(item: CueTrack, index: number)=>{ //获取cueinfo对应的filePath let globalVideoList = Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL) as VideoItem[]; this.curIndex = Utility.getCurIndexFromGlobalList(globalVideoList, cueinfo.filePath) this.currentSong = Utility.getItemByFilePath(globalVideoList, cueinfo.filePath) console.info('onecold cueinfo.filePath=', cueinfo.filePath) console.info('onecold this.currentSong =', JSON.stringify(this.currentSong)) if (this.currentSong){ if (this.CONTROL_PlayStatus !== PlayStatus.INIT) { this.stop();//播放之前先停止之前播放。 } this.songList = globalVideoList this.sonDataSource.pushArrayData(this.songList) this.videoUrl = this.currentSong.filePath this.name = item.title||this.currentSong.name this.cover = this.currentSong.pixelMapPath this.artist = item.performer||this.currentSong.artist this.currentSong.name = this.name this.currentSong.artist = this.artist AppStorage.setOrCreate('currentSong',this.currentSong) ; this.startPlayOrResumePlay(item.startOffset)//从cue的某个位置开始播放 } } }) } @Builder PlayController() { Column() { Line().width('100%').height(1).backgroundColor($r('app.color.index_background')) .visibility(this.isCustomizeBg ? Visibility.Hidden : Visibility.Visible) Row() { if(this.isHiCar()){ this.playConRigth() Column(){ this.centerName() }.layoutWeight(1) .onClick(()=>{ this.showPlayerView() }) this.playConLeft(false) }else{ this.playConLeft() this.playConRigth() } } .width('100%') .height(this.bottomBarHeight) .justifyContent(FlexAlign.Center) .hitTestBehavior(HitTestMode.Transparent) .backgroundColor(this.isCustomizeBg ? Color.Transparent : $r('app.color.bottom_control_background')) .bindContentCover($$this.isShowPlay, this.MusicPlayBuilder(), { // modalTransition: ModalTransition.DEFAULT, transition: AnimationHelper.transitionInDown(666), backgroundColor: 'rgba(0, 0, 0, 0.9)', }) .padding({ left: 16, right: 16 }) Line() .width('100%') .height(25) .backgroundColor($r('app.color.bottom_control_background'))// .visibility(this.isPhoneLan()?Visibility.None:this.isCustomizeBg?Visibility.Hidden:Visibility.Visible) .visibility(this.isCustomizeBg ? Visibility.Hidden : Visibility.Visible) } .clickEffect({ level: ClickEffectLevel.HEAVY }) } @State isFrontWhite: boolean = false @Builder centerName() { Column() { Text(this.currentSong?.name) .fontSize(16) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .maxLines(1) .fontColor($r('app.color.text_color')) .fontWeight(500); Row() { Text(this.currentSong?.artist) .margin({ top: 2 }) .fontSize(13) .textAlign(TextAlign.Start) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .fontColor($r('app.color.text_color')); } .visibility(this.currentSong?.artist? Visibility.Visible:Visibility.None) } .alignItems(HorizontalAlign.Center) } @Builder playConLeft(isLeft: boolean = true) { Row() { RotatingCover({ songLabel: this.currentSong?.pixelMapPath }); Column() { Text(this.currentSong?.name) .fontSize(16) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .maxLines(1) .fontColor($r('app.color.text_color')) .fontWeight(500); Row() { Text(this.currentSong?.artist) .margin({ top: 2 }) .fontSize(13) .textAlign(TextAlign.Start) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .fontColor($r('app.color.text_color')); } .visibility(this.currentSong?.artist? Visibility.Visible:Visibility.None) } .alignItems(isLeft?HorizontalAlign.Start:HorizontalAlign.End) .margin({ right: isLeft?22:10 }) .visibility(isLeft? Visibility.Visible:Visibility.None) } .padding({ right: 18 }) .layoutWeight(isLeft?1:0) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .onClick(() => { this.showPlayerView() }) } //打开播放页 showPlayerView() { if (ArrayUtil.isNotEmpty(this.songList)) { this.isShowPlay = true; let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc'; this.initLyric(lyricPath); this.startAutoHide(); } else { ToastUtil.showToast('当前播放列表为空,请先导入音乐。'); } } @Builder playConRigth() { Row() { Image($r('app.media.hm_previous')) .height(28) .width(28) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .margin({ left: 8, right: 16 }) .fillColor(this.themeColor) .displayPriority(2) .onClick(() => { if (ArrayUtil.isNotEmpty(this.songList)) { this.playPrevious() } else { ToastUtil.showToast('当前播放列表为空,请先导入音乐。') } }) //播放进度条 Stack() { Progress({ value: Math.floor(this.progressValue), type: ProgressType.Ring, }) .color(this.themeColor)// 进度条前景色为灰色 .backgroundColor($r('app.color.index_background')) .height(40) .aspectRatio(CommonConstants.ASPECT_RATIO) Image(this.CONTROL_PlayStatus === PlayStatus.PLAY ? $r('app.media.hm_pause') : $r('app.media.hm_play2')) .height(38) .width(38) .displayPriority(3) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .fillColor(this.themeColor) .onClick(() => { if (ArrayUtil.isNotEmpty(this.songList)) { console.info('onecold playOrPause 4642') this.playOrPause() } else { ToastUtil.showToast('当前播放列表为空,请先导入音乐。') } }) } Image($r('app.media.hm_next')) .height(28) .width(28) .margin({ right: 16, left: 16 }) .fillColor(this.themeColor) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .displayPriority(2) .onClick(() => { if (ArrayUtil.isNotEmpty(this.songList)) { this.playNext() } else { ToastUtil.showToast('当前播放列表为空,请先导入音乐。') } }) Image($r('app.media.hm_playlist')) .height(28) .width(28) .displayPriority(1) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .fillColor(this.themeColor) .bindSheet($$this.isShowSheet, this.PlayListSheet(), { height: '95%', dragBar: true, showClose: true, blurStyle: BlurStyle.Thin, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, backgroundColor: this.isPlayListBgGrass ? Color.Transparent : $r('app.color.silvery'), title: { title: Utility.resourceToString(this.context, $r('app.string.current_play_list')) + `(${this.songList.length}首)` } }) .onClick(() => { if (ArrayUtil.isNotEmpty(this.songList)) { this.isShowSheet = !this.isShowSheet; this.isFrontWhite = false LogUtil.info('onecold scrollToIndex = ' + this.curIndex) this.scrollIndex() } else { ToastUtil.showToast('当前播放列表为空,请先导入音乐。') } }) } } scrollIndex(){ if(this.curIndex>500)//条数太大的时候 滚动非常卡 导致闪退,先改为不滚 return try { setTimeout(() => { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.playListScroller.scrollToIndex(this.curIndex,true, ScrollAlign.CENTER) }) }, 123) }catch (error) { console.error(`onecold scrollIndex出错: ${error}`); } } //播放列表布局 @Builder PlayListSheet() { Column() { this.PlayList() } .width('100%') .height('100%') } @State titleStr: string = '' @State ablumStr: string = '' @State artistStr: string = '' @State lyricConStr: string = '' @State currentEditItem: string = '' @State yearStr: string = '' @State trackStr: string = '' @State genreStr: string = '' @State imagePathStr: string = '' @State albumArtistStr: string = '' @State composerStr: string = '' @State lyricistStr: string = '' @State commentStr: string = '' @State discStr: string = '' doUpdateData() { setTimeout(() => { if (this.modeType === 0) { this.deleteCache(this.currentPath) this.getSortedFiles(this.currentPath) } workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName }); workerInstance.postMessage({ code: 3, data: this.context }); workerInstance.postMessage({ code: 4, data: this.context }); }, 999) } //编辑信息 async doEdit(item: VideoItem) { if (!item) { return } if (StrUtil.isEmpty(this.titleStr)) { ToastUtil.showToast('标题不能为空') return } //用户更改了封面,那就内嵌下封面 if(StrUtil.isNotEmpty(this.imagePathStr)){ const resultCover:boolean = await changeMusicCover(this.context, item.filePath,this.imagePathStr,true); this.imagePathStr = '' if(resultCover){ ToastUtil.showToast('内嵌封面成功') } } const metadata:FFMpegTags = { title: this.titleStr, artist: this.artistStr, album: this.ablumStr, TYER:this.yearStr, genre:this.genreStr, track:this.trackStr, album_artist:this.albumArtistStr, TPE2:this.albumArtistStr,//ID3v2 中通常没有专门的 "专辑艺术家" 字段,但有些软件会使用 TPE2 来表示专辑艺 COMPOSER:this.composerStr, lyricist:this.lyricistStr, TEXT:this.lyricistStr,//ID3v2 使用 TEXT 字段来表示作词者 comment:this.commentStr, disc:this.discStr, }; //内嵌下标签的值,设置overwrite为true会直接修改原文件 const result:boolean = await repairAudioMetadata( item.filePath, this.lyricConStr, metadata, true ); if (result) { this.table.updateMediaInfo(item.filePath, this.titleStr, this.artistStr, this.ablumStr,this.lyricConStr, this.yearStr,this.genreStr,this.trackStr,this.albumArtistStr,this.composerStr, this.lyricistStr,this.commentStr,this.discStr, (success: boolean, error?: string) => { // this.isShowEdit = false if (success) { console.log(" onecold 编辑信息成功,数据库已同步"); ToastUtil.showToast('内嵌音乐标签成功') this.isShowMoreView = false this.name = this.titleStr if (item.filePath == this.currentSong?.filePath) { this.currentSong.name = this.titleStr this.artist = this.artistStr this.currentSong.artist = this.artistStr this.currentSong.album = this.ablumStr this.currentSong.year = this.yearStr this.currentSong.genre = this.genreStr this.currentSong.track = this.trackStr this.currentSong.lyricContent = this.lyricConStr this.initLyric(item.filePath) this.currentSong.ALBUMARTIST = this.albumArtistStr this.currentSong.COMPOSER = this.composerStr this.currentSong.LYRICIST = this.lyricistStr this.currentSong.COMMENT = this.commentStr } this.doUpdateData() } else { // ToastUtil.showToast('保存失败' + error?.toString()) console.error(" onecold 编辑信息数据库失败原因: " + error); } }); console.log(' onecold 元数据标签更新成功,'); } else { ToastUtil.showToast('内嵌音乐标签失败') console.log('onecold 内嵌音乐标签失败'); } this.longItemFilePath = '' this.tempLyricContent = '' this.isShowEdit = false } updateLyricToDB() { } @Builder editSheet(item: VideoItem) { Scroll() { Column() { this.editDetail(item) } } .width('100%') .height('100%') } @Builder editDetail(item: VideoItem) { Column() { if (item) { Row() { Stack() { Image(StrUtil.isNotEmpty(this.imagePathStr)?this.imagePathStr.startsWith('http')? this.imagePathStr:fileUri.getUriFromPath(this.imagePathStr): StrUtil.isEmpty(item.pixelMapPath) ? $r('app.media.add_image2') : item.pixelMapPath) .fillColor(this.themeColor) .height(88) .width(88) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.6}) .borderRadius(12) .clip(true) .opacity(this.opacityItem)// 绑定透明度 .margin({ left: 25 }) } .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.6}) .onClick(async () => { const imagePath:string | undefined = await this.goSelectImage(item); if (imagePath) { //用户更改过图片 this.imagePathStr = imagePath } }) .width('28%') Row() { Column() { Text(item.name) .fontSize(17) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .animation({ duration: 555, curve: 'Linear', }) .fontColor(this.themeColor) .margin({ left: 8 }) Row() { Text(item.artist) .fontSize(14) .maxLines(1) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .padding({ top: 8 }) .fontColor(this.themeColor) .margin({ left: 8 }) } } .height('100%') .width(100) .layoutWeight(1) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) Column() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row(){ SymbolGlyph($r('sys.symbol.picture')) .fontSize(18) .fontColor([Color.White]) Text('获取封面') .margin({ left: 4 }) .fontSize(11) .fontColor(Color.White) .fontWeight(480) .textAlign(TextAlign.Center) } .justifyContent(FlexAlign.Center) } .height(38) .width(100) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ right: 35 }) .onClick(async () => { if (PreferencesUtil.getStringSync('COVER_API', '') === '') { this.showTipsDialog(); return; // Return empty string when no API is configured } const imagePath:string | undefined = await this.doSearchCover(item); console.info('onecold imagePath = '+imagePath) if (StrUtil.isNotEmpty(imagePath)) { //用户更改过图片 this.imagePathStr = imagePath ToastUtil.showToast('获取封面成功') }else{ ToastUtil.showToast('获取封面失败,请重试') } }) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row(){ SymbolGlyph($r('sys.symbol.input_mode')) .fontSize(18) .fontColor([Color.White]) Text('获取歌词') .margin({ left: 4 }) .fontSize(11) .fontColor(Color.White) .fontWeight(480) .textAlign(TextAlign.Center) } .justifyContent(FlexAlign.Center) } .height(38) .width(100) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ top: 9, right: 35 }) .onClick(async () => { //判断有没有设置api let apiUrl = PreferencesUtil.getStringSync('LRC_API', '') if (StrUtil.isEmpty(apiUrl)) { this.isLyricSetting = false this.showTipsDialog() return } let artist = item.artist let title = item.name if(!artist) artist = '' if(!title) title = '' let res: string | undefined = await getApiLyric(apiUrl,title, artist, false); if (res && StrUtil.isNotEmpty(res) && res !== 'unknown' && res !== 'Timeout was reached') { this.lyricConStr = res; ToastUtil.showToast('获取歌词成功') }else{ ToastUtil.showToast('获取歌词失败,请重试') } }) } .justifyContent(FlexAlign.Start) } .height('100%') .justifyContent(FlexAlign.Start) .layoutWeight(1) } .width('100%') .height(98) Row() { Text('标题:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.name }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.titleStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('艺术家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.artist }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.artistStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('专辑名:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.album }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.ablumStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Column(){ Column(){ Text('歌词:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ top:9,left: 22 }) Button({ type: ButtonType.Capsule, stateEffect: true }) { SymbolGlyph($r('sys.symbol.trash_fill')) .fontSize(25) .alignSelf(ItemAlign.Center) .margin({ left: 22,top:25}) } .backgroundColor(Color.Transparent) .visibility(StrUtil.isEmpty(this.tempLyricContent)&&StrUtil.isEmpty(this.lyricConStr)? Visibility.None:Visibility.Visible) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(()=>{ this.lyricConStr = '' this.lyricContent = '' this.tempLyricContent = '' }) Button({ type: ButtonType.Capsule, stateEffect: true }) { SymbolGlyph($r('sys.symbol.plus')) .fontSize(25) .alignSelf(ItemAlign.Center) .margin({ left: 22,top:25}) } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick( ()=>{ this.callFilePickerSelectFileForLyric() }) Button({ type: ButtonType.Capsule, stateEffect: true }) { SymbolGlyph($r('sys.symbol.traditional_square')) .fontSize(25) .alignSelf(ItemAlign.Center) .margin({ left: 22,top:25}) } .backgroundColor(Color.Transparent) .visibility(StrUtil.isEmpty(this.tempLyricContent)&&StrUtil.isEmpty(this.lyricConStr)? Visibility.None:Visibility.Visible) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick( ()=>{ let lyricC = StrUtil.isNotEmpty(this.lyricConStr)?this.lyricConStr: this.tempLyricContent this.lyricConStr = getTransverterText(lyricC,1)//1是简体转繁体 ,2是繁体转简体 }) Button({ type: ButtonType.Capsule, stateEffect: true }) { Image($r('app.media.jianti')) .height(25) .fillColor($r('app.color.text_color')) .alignSelf(ItemAlign.Center) .margin({ left: 22,top:25}) } .backgroundColor(Color.Transparent) .visibility(StrUtil.isEmpty(this.tempLyricContent)&&StrUtil.isEmpty(this.lyricConStr)? Visibility.None:Visibility.Visible) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick( ()=>{ let lyricC = StrUtil.isNotEmpty(this.lyricConStr)?this.lyricConStr: this.tempLyricContent this.lyricConStr = getTransverterText(lyricC,2)//1是简体转繁体 ,2是繁体转简体 }) } .layoutWeight(1) } TextArea({ text: StrUtil.isNotEmpty(this.lyricConStr)?this.lyricConStr: this.tempLyricContent }) .type(TextAreaType.NORMAL) .height(StrUtil.isEmpty(this.lyricConStr)&&StrUtil.isEmpty(this.tempLyricContent)?100:'auto') .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.lyricConStr = val }) } .width('100%') .height(StrUtil.isEmpty(this.lyricConStr)&&StrUtil.isEmpty(this.tempLyricContent)?120:'auto') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('年份:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.year }) .height(40) .maxLines(1) .fontSize(14) .type(InputType.Number) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.yearStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('音轨号:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text:item.track=='未知'?'': item.track }) .height(40) .maxLines(1) .fontSize(14) .type(InputType.Number) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.trackStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('碟号:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text:item.disc }) .height(40) .maxLines(1) .fontSize(14) .type(InputType.Number) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.discStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('风格:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.genre }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.genreStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('专辑艺术家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.ALBUMARTIST }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.albumArtistStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('作曲:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.COMPOSER }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.composerStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('作词:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.LYRICIST }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.lyricistStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('注释:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) TextInput({ text: item.COMMENT }) .height(40) .maxLines(1) .fontSize(14) .layoutWeight(1) .fontColor($r('app.color.text_color')) .margin({ right: 20, left: 10 }) .onChange((val: string) => { this.commentStr = val }) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Button('保存') .fontColor(Color.White) .layoutWeight(1) .height(50) .width(100) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ right: 20, bottom: 20 }) .onClick(async () => { this.doEdit(item) }) Button('取消') .fontColor(Color.White) .layoutWeight(1) .height(50) .width(100) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ left: 20, bottom: 20 }) .onClick(async () => { if(this.isGridMusic){ this.longItemFilePath ='' }else{ this.isShowEdit = false } this.setEditStrEmpty() if(this.isShowPlay){ this.isShowEdit = false } }) } .margin({ left: 38, right: 38, top: 10, bottom: 10 }) .width(250) .height(60) } } .margin({ bottom: 20 }) } setEditStrEmpty(){ this.imagePathStr = '' this.lyricConStr = '' this.artistStr = '' this.titleStr = '' this.ablumStr = '' this.genreStr = '' this.yearStr = '' this.trackStr = '' this.albumArtistStr = '' this.composerStr = '' this.lyricistStr = '' this.commentStr = '' this.discStr = '' } @Builder detailSheet(item:VideoItem) { Scroll() { Column() { this.songDetail(item) } } .width('100%') .height('100%') } @Builder songDetail(currentItem:VideoItem) { Column() { if (currentItem) { Row() { Stack() { Image(StrUtil.isEmpty(currentItem.pixelMapPath) ? $r('app.media.music_red') : currentItem.pixelMapPath) .fillColor(this.themeColor) .height(80) .width(80) .borderRadius(10) .opacity(this.opacityItem)// 绑定透明度 .margin({ left: 22 }) } Column() { Column() { Text(currentItem.name) .fontSize(16) .maxLines(1) .animation({ duration: 555, curve: 'Linear', }) .fontColor($r('app.color.text_color')) .margin({ left: 15 }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) } .height('100%') } .width('100%') .height(90) .justifyContent(FlexAlign.SpaceBetween) Row() { Text('艺术家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem?.artist ? currentItem.artist : '未知歌手') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 10 }) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('专辑名:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem?.album ?currentItem.album : '未知专辑') .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('时长:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(StrUtil.isEmpty(currentItem.duration)? (this.isShowDetailMore?this.totalTime:'未知'):currentItem.duration) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('采样率:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(Utility.convertToKHz(currentItem.sampleRate)) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('比特率:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.bit_rate) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('位深:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(`${currentItem.bits_per_raw_sample} bits`) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('声道:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(`${currentItem.channels}`) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('声道布局:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(`${currentItem.channel_layout}`) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('起始播放点:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(`${currentItem.start_time}`) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('风格:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.genre) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('发行时间:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.year) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('质量评分:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.probe_score+"") .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('音轨号:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.track) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('碟号:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.disc) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('专辑艺术家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.ALBUMARTIST) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('作曲家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.COMPOSER) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('作词家:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.LYRICIST) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('注释:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.COMMENT) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('格式:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(Utility.formatMimeType(currentItem.mimeType)) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('播放次数:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.playCount?.toString()) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('流数量:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.nb_streams?.toString()) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('文件大小:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.size) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('文件名:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.fileName) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('修改时间:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.cTime) .fontSize(14) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .width('100%') .margin({ top: 10, bottom: 10 }) .justifyContent(FlexAlign.Start) Row() { Text('存放目录:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.filePath) .fontSize(12) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .margin({ top: 10, bottom: 10 }) .width('100%') .justifyContent(FlexAlign.Start) Row() { Text('内嵌歌词:') .fontSize(14) .fontColor($r('app.color.text_color')) .margin({ left: 22 }) Text(currentItem.lyricContent) .fontSize(12) .copyOption(CopyOptions.InApp) .margin({ left: 10 }) .fontColor($r('app.color.text_color')) .layoutWeight(1) } .margin({ top: 10, bottom: 10 }) .width('100%') .justifyContent(FlexAlign.Start) } } .margin({ bottom: 20 }) } //定时关闭布局 @Builder TimeCloseSheet() { Scroll() { Column() { this.TimeClose() } } .width('100%') .height('100%') } //倒计时关闭音乐,退出App功能实现。 @State selectedMinutes: number = 0 // 0表示未开启 @State remainingSeconds: number = 0 // 预设时间选项 private presetTimes: number[] = [0, 5, 15, 30, 45, 60, -1] private formatTime(seconds: number): string { const mins = Math.floor(seconds / 60) const secs = seconds % 60 return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` } // 开始倒计时 private handleTimeSelect(minutes: number) { if (minutes === this.selectedMinutes) { this.stopCountdown(); this.selectedMinutes = 0; return; } this.selectedMinutes = minutes; this.remainingSeconds = minutes * 60; this.startCountdown(); // 新增调用 } @Builder TimeClose() { Column() { // 倒计时显示区域 Text(this.selectedMinutes > 0 ? `剩余时间:${this.formatTime(this.remainingSeconds)}` : "定时关闭未开启") .fontSize(17) .fontColor($r('app.color.text_color')) .margin({ top: 2, bottom: 8 }); Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) { ForEach(this.presetTimes, (minutes: number, index: number) => { // 单行时间选项容器 Row() { // 文字描述区域 Text(this.getDisplayText(minutes)) .fontSize(15) .fontColor(minutes === this.selectedMinutes ? this.themeColor : $r('app.color.text_color'))// 选中态颜色变化 .textAlign(TextAlign.Start) .layoutWeight(1); // 占据剩余空间 // 复选框样式的选择器 Row() { if (minutes === this.selectedMinutes) { // 选中状态 - 带勾号的圆形复选框 Row() { // 使用系统内置的勾号图标 Image($r('sys.media.ohos_ic_public_ok')) .width(14) .height(14) .fillColor(Color.White) } .width(24) .height(24) .borderRadius(12) .backgroundColor(this.themeColor) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) } else { // 未选中状态的圆形边框 Row() .width(24) .height(24) .borderRadius(12) .border({ width: 2, color: $r('app.color.text_color'), style: BorderStyle.Solid }) .backgroundColor(Color.Transparent) } } .width(24) .height(24) .margin({ left: 20 }) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) } .width('100%') .padding({ top: 18, bottom: 18, left: 18, right: 18 }) .backgroundColor(minutes === this.selectedMinutes ? $r('app.color.timeColor_bg') : $r('app.color.bottom_control_background')) // 选中背景高亮 .borderRadius(12) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ bottom: 18 }) .onClick(() => this.handleSelect(minutes)); // 点击整行触发选择 }); } .width('100%') .padding(16) } } // 获取显示文本 private getDisplayText(minutes: number): string { if (minutes === -1) { return Utility.resourceToString(getContext(this), $r('app.string.customize')); } return minutes === 0 ? Utility.resourceToString(getContext(this), $r('app.string.close')) : `${minutes}` + Utility.resourceToString(getContext(this), $r('app.string.minute_after')); } // 处理选择事件 private handleSelect(minutes: number): void { if (minutes === -1) { // 自定义 // 弹出自定义时间对话框 DialogHelper.showTextInputDialog({ title: '自定义时间', maskColor: Color.Transparent, inputType: InputType.Number, text: '', onChange: (text) => { console.error("onChange: " + text); }, onAction: (action, dialogId, content) => { if (action == DialogAction.TWO) { const customMinutes = Number(content); if (customMinutes > 0) { this.handleTimeSelect(customMinutes); // 设置自定义时间 ToastUtil.showToast("设置成功," + customMinutes + "分钟后定时关闭"); } } } }); } else { this.handleTimeSelect(minutes); // 统一调用选择处理方法 } } // 添加定时器引用 private timerId: number = -1; // 启动/重置倒计时 private startCountdown() { // 清除旧定时器 if (this.timerId !== -1) { clearInterval(this.timerId); } // 创建新定时器 if (this.selectedMinutes > 0) { this.timerId = setInterval(() => { if (this.remainingSeconds === 15) { this.showConfirmationDialog(); } if (this.remainingSeconds > 0) { this.remainingSeconds -= 1; } else { this.stopCountdown(); this.handleTimeout(); } }, 1000); } } // 15倒计时后再次提示显示确认是否关闭App对话框 private showConfirmationDialog() { DialogHelper.showAlertDialog({ backgroundColor: $r('app.color.btn_green'), title: '确认关闭', maskColor: Color.Transparent, content: "马上到达定时关闭时间,您是否确定要退出应用?", borderStyle: BorderStyle.Dashed, primaryButton: '暂不关闭', secondaryButton: '确定', onAction: (action) => { if (action == DialogAction.ONE) { //暂不关闭,取消定时 this.stopCountdown(); this.selectedMinutes = 0 } else if (action == DialogAction.TWO) { } } }) } // 停止倒计时 private stopCountdown() { if (this.timerId !== -1) { clearInterval(this.timerId); this.timerId = -1; } } // 超时处理 private handleTimeout() { // 执行播放器关闭逻辑 this.selectedMinutes = 0; ToastUtil.showToast(" 定时关闭已完成"); this.stop(); // 假设存在的播放器实例 //退出APP const mContext = getContext(this) as common.UIAbilityContext mContext.terminateSelf(); AvSessionController.getInstance(true).unregisterSessionListener() } //倒计时关闭音乐,退出App功能实现结束。 //播放列表的拖动List 重新排序 @State sonDataSource: LazyDataSource = new LazyDataSource(this.songList) //左滑操作 移动文件 重命名 删除等操作 @Builder sonButton(item: VideoItem, index: number, filePath: string) { Row() { //加入下一首播放 Button() { Image($r('app.media.next_to_play')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .backgroundColor(this.themeColor) .margin(5) .onClick(() => { this.addToNextPlay(item); }) Button() { Image($r('app.media.close')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .onClick(() => { this.songList.splice(Number(index), 1) this.sonDataSource.pushArrayData(this.songList) }) Button() { Image($r('app.media.share2')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .onClick(() => { Utility.doShareMusic(item, getContext(this) as common.UIAbilityContext) }) } } itemMoveSon(index: number, newIndex: number): void { if (newIndex < 0 || newIndex >= this.songList.length) { return; } let tmp = this.songList.splice(index, 1); this.songList.splice(newIndex, 0, tmp[0]); this.sonDataSource.pushArrayData(this.songList) this.curIndex = Utility.getIndexFromList(this.songList, this.videoUrl) } @Builder PlayList() { List({ scroller: this.playListScroller }) { LazyForEach(this.sonDataSource, (item: VideoItem, index: number) => { ListItem() { this.MusicItemSon(item, index) } .swipeAction({ end: this.sonButton(item, index, item.filePath) }) //左滑 .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.MIDDLE }) //拖动List关键代码开始 .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) }) .zIndex(this.dragItem === index ? 1 : 0) .translate(this.dragItem === index ? { y: this.offsetY } : { y: 0 }) .gesture( GestureGroup(GestureMode.Sequence, LongPressGesture({ repeat: true }) .onAction((event?: GestureEvent) => { animateTo({ curve: Curve.Friction, duration: 300 }, () => { this.scaleItem = index }) }) .onActionEnd(() => { animateTo({ curve: Curve.Friction, duration: 300 }, () => { this.scaleItem = -1 }) }), PanGesture({ fingers: 1, direction: null, distance: 0 }) .onActionStart(() => { this.dragItem = index // 记录当前拖动项索引 this.dragRefOffset = 0 // 重置偏移基准 }) .onActionUpdate((event: GestureEvent) => { this.offsetY = event.offsetY - this.dragRefOffset; this.neighborItem = -1; let indexA = this.songList.indexOf(item); // LogUtil.info('onecold indexA = '+indexA +' index=='+index) let curveValue: curves.ICurve = curves.initCurve(Curve.Sharp); let value: number = 0; if (this.offsetY < 0) { value = curveValue.interpolate(-this.offsetY / this.ITEM_INTV); this.neighborItem = indexA - 1; this.neighborScale = 1 - value / 20; } else if (this.offsetY > 0) { value = curveValue.interpolate(this.offsetY / this.ITEM_INTV); this.neighborItem = indexA + 1; this.neighborScale = 1 - value / 20; } if (this.offsetY > this.ITEM_INTV / 2 && indexA + 1 < this.songList.length) { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.offsetY -= this.ITEM_INTV; this.dragRefOffset += this.ITEM_INTV; this.itemMoveSon(indexA, indexA + 1); }); } else if (this.offsetY < -this.ITEM_INTV / 2 && indexA - 1 >= 0) { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.offsetY += this.ITEM_INTV; this.dragRefOffset -= this.ITEM_INTV; this.itemMoveSon(indexA, indexA - 1); }); } }) .onActionEnd((event: GestureEvent) => { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.dragItem = -1 this.neighborItem = -1 }) animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => { this.scaleItem = -1 }) }) ) .onCancel(() => { animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { this.dragItem = -1 this.neighborItem = -1 }) animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => { this.scaleItem = -1 }) }) ) //拖动List关键代码结束 .onClick(() => { this.doPlay(item, index, true) }) }, (item: VideoItem) => item.filePath) } .width('100%') .height('100%') // .transition(TransitionEffect.move(TransitionEdge.BOTTOM) // .animation({ duration: 500, curve: Curve.Ease })) .cachedCount(10) .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 }) .gesture(PinchGesture({ fingers: 2 }) .onActionEnd((event: GestureEvent) => { this.setSonListTwoFingers() }) .onActionUpdate(e => { this.scaleValue = this.pinchValue * e.scale }) .onActionStart(e => { })) .padding({ bottom: 0 }) } setSonListTwoFingers() { this.pinchValue = this.scaleValue; Logger.info('this.pinchValue = ' + this.pinchValue); if (this.pinchValue >= 1) { this.sonTwoFingerType++ Logger.info('this.twoFingerType1 = ' + this.sonTwoFingerType); if (this.sonTwoFingerType > 3) { this.sonTwoFingerType = 1 PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic) } } else { this.sonTwoFingerType-- Logger.info('this.twoFingerType2 = ' + this.sonTwoFingerType); if (this.sonTwoFingerType < 1) { this.sonTwoFingerType = 1 } } PreferencesUtil.put('sonTwoFingerType', this.sonTwoFingerType) this.pinchValue = 1; this.scaleValue = 1; } @State sonTwoFingerType:number = 3 @Builder private MusicItemSon(item: VideoItem, index?: number) { Button({ type: ButtonType.Normal, stateEffect: true }) { Row() { Stack() { Image(StrUtil.isEmpty(item.pixelMapPath) ? $r('app.media.music_red') : item.pixelMapPath) .height(this.sonTwoFingerType == 3||this.sonTwoFingerType==4 ? 55 : this.sonTwoFingerType == 2 ? 48 : 40) .width(this.sonTwoFingerType == 3||this.sonTwoFingerType==4 ? 55 : this.sonTwoFingerType == 2 ? 48 : 40) .alt($r('app.media.music_red')) .fillColor(this.themeColor) .borderRadius('100%') .clip(true) .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿 .draggable(false)// 禁止长按手势拖动 .autoResize(true) // 重采样,可减少内存占用 .margin({ left: 20 }) } .width('15%') Column() { Column() { Text(this.isShowFileName?item.fileName :item.name) .fontSize(this.sonTwoFingerType == 1 ? 12 : this.sonTwoFingerType == 2 ? 15 : 18) .fontColor(this.curIndex === index ? this.themeColor : this.isFrontWhite ? Color.White : $r('app.color.text_color')) .maxLines(1) .margin({ left: this.sonTwoFingerType == 3 ? 18 : 10 }) Row() { Column(){ Text(item.md5Str?.includes('Lossless')? Utility.resourceToString(this.context,$r('app.string.lossless')):item.md5Str)//音质 .fontSize(this.sonTwoFingerType == 1 ? 8 : this.sonTwoFingerType == 2 ? 9 : 11) .padding({ top: 3,right:6,left:6,bottom:3 }) .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color')) .fontWeight(500) .borderRadius(12) .margin({ left: this.sonTwoFingerType == 3 ? 12 : 10 }) .backgroundColor('#FFC107') } .padding({ top: 8 }) Text(StrUtil.isEmpty(item.artist) ? item.duration : this.sonTwoFingerType<=2?" "+item?.duration: item.artist + ' ' + item?.album) .fontSize(this.sonTwoFingerType == 1 ? 11 : this.sonTwoFingerType == 2 ? 12 : 14) .padding({ top: 8 }) .maxLines(1) .fontColor(this.curIndex === index ? this.themeColor : this.isFrontWhite ? $r('app.color.playlistText_color') : $r('app.color.text_color')) .margin({ left: this.sonTwoFingerType == 3 ? 5 : 3 }) Blank() Text(item.size) .fontSize(this.sonTwoFingerType == 1 ? 11 : this.sonTwoFingerType == 2 ? 12 : 14) .padding({ top: 8 }) .maxLines(1) .visibility(StrUtil.isEmpty(item.artist) ? Visibility.Visible : Visibility.None) .fontColor(this.curIndex === index ? $r('app.color.title_bar_bg_text') : this.isFrontWhite ? $r('app.color.playlistText_color') : $r('app.color.text_color')) .margin({ left: 10 }) } } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) } .height('100%') .width('65%') Column() { ImageAnimator() .images(this.imagesF)// 动画数组 .duration(1000)// 持续 .state(this.animationState)// 动画状态 .fillMode(FillMode.Forwards) .width(18) .visibility(this.videoUrl === item.filePath ? this.isMultiSelect ? Visibility.None : Visibility.Visible : Visibility.None) .height(18) .iterations(-1) // 播放次数 } .width('20%') } } .backgroundColor(Color.Transparent) .width('100%') .reuseId('son_item') .height(this.sonTwoFingerType == 3 ? ITEM_HEIGHT_BIG : this.sonTwoFingerType == 2 ? ITEM_HEIGHT+10 : ITEM_HEIGHT_SMALL+10) .shadow(this.scaleItem === index ? { radius: 70, color: '#15000000', offsetX: 0, offsetY: 0 } : { radius: 0, color: '#15000000', offsetX: 0, offsetY: 0 }) .animation({ curve: Curve.Sharp, duration: 300 }) } @Builder MusicPlayBuilder() { Column() { this.IjkMusicPlayerView() } .height('100%') .width('100%') .onDisAppear(() => { this.translateY = 0; }) .translate({ y: this.translateY }) .gesture( PanGesture(this.panOption) .onActionUpdate((event?: GestureEvent) => { if (event) { // 只允许向下滑动,向上滑动时限制为0 this.translateY = Math.max(0, event.offsetY); } }) .onActionEnd((event?: GestureEvent) => { // 使用更低的阈值和滑动速度判断 const minDistance = 100; // 最小滑动距离 100vp const minVelocity = 500; // 最小滑动速度 // 获取滑动速度(如果可用) const velocity = event?.velocityY || 0; // 判断是否应该关闭:距离足够 或者 速度足够快 const shouldClose = this.translateY > minDistance || velocity > minVelocity; if (shouldClose) { // 添加关闭动画 this.getUIContext().animateTo({ duration: 300, curve: Curve.Smooth }, () => { this.isShowPlay = false; this.translateY = 0; }) } else { // 回弹动画 this.getUIContext().animateTo({ duration: 300, curve: Curve.Smooth }, () => { this.translateY = 0; }) } }) ) } @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD; //一多界面适配 @StorageProp('currentHeightBreakpoint') currentHeightBreakpoint: HeightBreakpoint | undefined = HeightBreakpoint.HEIGHT_LG; @StorageProp('currentWidthBreakpoint') currentWidthBreakpoint: WidthBreakpoint | undefined = WidthBreakpoint.WIDTH_SM; @State topBarHeight: number = 50 @State barOpacity: number = 1 @State barBottomOpacity: number = 1 private hideDone: boolean = false; @State currentYOffset: number = 0; @State bottomBarHeight: number = 60; private isHiding: boolean = false; //是否是Pura X外屏,或者小屏幕 isPuraWP() { return this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_MD } //是否是Pura X外屏,或者小屏幕,手机横屏 /** * 是否是Pura X外屏,或者小屏幕,手机横屏 * @returns */ isCoverOpacity() { if (this.isPuraWP()) { return true } if (this.isPhoneLan()) { return true } if (deviceInfo.marketName.includes('Pura X') && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_SM) { return true } return false } //是不是手机横屏 isPhoneLan() { // LogUtil.info('twocold this.currentHeightBreakpoint = '+this.currentHeightBreakpoint) // LogUtil.info('twocold this.currentWidthBreakpoint = '+this.currentWidthBreakpoint) LogUtil.info('hicar isPhoneLan width= ' + this.windowWidth); LogUtil.info('hicar isPhoneLan height= ' + this.windowHeight); LogUtil.info(`twocold hicarWindow size: ${this.windowWidth}x${this.windowHeight}, this.isHiCarStatus: ${this.isHiCarStatus}`); LogUtil.info(`twocold hicar Window size: ${this.windowWidth}x${this.windowHeight}, isHiCarSmall: ${this.isHiCarSmall()}`); if (this.isHiCarKuanBianPing()) { return true } if (this.isHiCar()) { return false } if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE && this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_SM && this.currentWidthBreakpoint == WidthBreakpoint.WIDTH_MD&&this.isLandscape) { //如果是手机横屏,返回true return true } return false } //是不是1920x720的hicar的屏幕分辨率 isHiCarKuanBianPing(){ if(!this.isHiCar()){ return false } const hiCarAspectRatios: HiCarAspectRatio[] = [ { ratio: 1920 / 720, name: "1920x720" }, ]; const currentRatio: number = this.windowWidth / this.windowHeight; const RATIO_TOLERANCE: number = 0.22; // 宽高比容差 LogUtil.info('twocold isHiCarKuanBianPing currentRatio = ' + currentRatio) for (const hiCarRatio of hiCarAspectRatios) { LogUtil.info('twocold isHiCarKuanBianPing hiCarRatio= ' + hiCarRatio.ratio) LogUtil.info('twocold isHiCarKuanBianPing Math.abs(currentRatio - hiCarRatio.ratio)= ' + Math.abs(currentRatio - hiCarRatio.ratio)) if (Math.abs(currentRatio - hiCarRatio.ratio) <= RATIO_TOLERANCE) { // LogUtil.info(`HiCar detected: ${hiCarRatio.name}, actual: ${this.windowWidth}x${this.windowHeight}`); //宽扁平的尺寸设置列表展示 this.twoFingerType ==3 this.isGridMusic = false return true; } } return false; } //是不是hicar的屏幕分辨率 isHiCarSmall() { if(!this.isHiCar()){ return false } if(this.windowWidth>1800&&this.windowHeight>=1200)//大屏幕可以Grid { this.columns = 6 return false } const hiCarAspectRatios: HiCarAspectRatio[] = [ { ratio: 800 / 480, name: "800x480" }, { ratio: 1280 / 720, name: "1280x720" }, { ratio: 1920 / 1080, name: "1920x1080" }, ]; const currentRatio: number = this.windowWidth / this.windowHeight; const RATIO_TOLERANCE: number = 0.22; // 宽高比容差 LogUtil.info('twocold isHiCarSmall currentRatio = ' + currentRatio) for (const hiCarRatio of hiCarAspectRatios) { LogUtil.info('twocold isHiCarSmall hiCarRatio= ' + hiCarRatio.ratio) LogUtil.info('twocold isHiCarSmall Math.abs(currentRatio - hiCarRatio.ratio)= ' + Math.abs(currentRatio - hiCarRatio.ratio)) LogUtil.info('twocold isHiCarSmall isHiCarStatus= ' + this.isHiCarStatus); if (Math.abs(currentRatio - hiCarRatio.ratio) <= RATIO_TOLERANCE) { // LogUtil.info(`HiCar detected: ${hiCarRatio.name}, actual: ${this.windowWidth}x${this.windowHeight}`); //宽扁平的尺寸设置列表展示 return true; } } return false; } isHiCar() { return this.isHiCarStatus&&this.curDisplayIsHiCar; } //是不是正常的手机竖屏 isPhonePortrait() { // LogUtil.info('twocold this.currentHeightBreakpoint = ' + this.currentHeightBreakpoint) // LogUtil.info('twocold this.currentWidthBreakpoint = ' + this.currentWidthBreakpoint) // LogUtil.info('Heanup 当前设备类型:' + DeviceUtil.getDeviceType()) if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE && this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG && this.currentWidthBreakpoint == WidthBreakpoint.WIDTH_SM) { //如果是手机横屏,返回false return true } return false } @State showSpeedDialog: boolean = false @State longPressSpeed: number = 3 //长按默认倍数 @Builder SpeedDialog() { Stack() { Column() { Text('已切换至' + this.longPressSpeed + '倍速播放') .fontColor(Color.White) .visibility(Visibility.Hidden) } .backgroundColor(Color.Black) .borderRadius(20) .padding({ top: 10, right: 20, left: 15, bottom: 20 }) .opacity(0.6) Text('已切换至' + this.longPressSpeed + '倍速播放') .fontColor(Color.White) } .transition(TransitionEffect.OPACITY.animation({ duration: 500, curve: Curve.Ease })) .zIndex(5) .margin({ top: 80 }) } @Builder IjkMusicPlayerView() { Column() { Column() { Column() { //播放标题 Column() { this.PlayTitle() } .position({ x: 0, y: this.isCoverOpacity() ? 15 : 40 }) .width('100%') .margin({ top: 0 }) .height(PlayConstants.HEIGHT) .visibility(DeviceUtil.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_2IN1 ? Visibility.Visible : this.isShowPlayPageBack ? Visibility.Visible : Visibility.None) if (this.showSpeedDialog) { this.SpeedDialog() } Stack() { //手势拖动快进 Column() .width(CommonConstants.FULL_PERCENT) .height('100%') .gesture( GestureGroup(GestureMode.Parallel, // 长按屏幕倍数播放 LongPressGesture({ repeat: true }) .onAction(() => { if (this.CONTROL_PlayStatus === PlayStatus.PLAY) { this.showSpeedDialog = true; this.mIjkMediaPlayer.setSpeed(this.longPressSpeed + 'f'); } }) .onActionEnd(() => { this.showSpeedDialog = false; this.mIjkMediaPlayer.setSpeed(this.playSpeed + 'f'); }), //双击屏幕暂停或者播放 TapGesture({ count: 2 }) .onAction((event: GestureEvent) => { if (this.CONTROL_PlayStatus === PlayStatus.PLAY) { ToastUtil.showToast('已暂停') } console.info('onecold playOrPause 6693') this.playOrPause() }) ) ) } .height('65%') Swiper() { this.CoverInfo() this.PlayerLyrics() } .onChange((index: number) => { this.currentSwiperIndex = index }) .indicator(false) // .indicator(this.currentBreakpoint === BreakpointTypeEnum.SM ? // new DotIndicator() // .top(this.isCoverOpacity()?0:10) // // .selectedColor($r('app.color.select_swiper')) // .color($r('app.color.slider_track')):false // ) .displayCount(new BreakpointType({ sm: 1, md: 2, lg: 2, xl: 2, xxl: 2 }).getValue(this.currentBreakpoint)) .clip(false) .loop(false) .autoPlay(false) // .interval(8000) .position({ x: 0, y: this.isCoverOpacity() ? 30 : 40 }) .height('100%') .hitTestBehavior(HitTestMode.Transparent) Image($r('app.media.icon_replay')) .objectFit(ImageFit.Auto) .width('120px') .height('120px') .position({ x: '45.1%', y: '47.5%' })// .visibility(this.replayVisible) .border({ width: 0 }) .visibility(Visibility.None) .borderStyle(BorderStyle.Dashed)// .hitTestBehavior(HitTestMode.Transparent) .onClick(() => { console.info('onecold startPlayOrResumePlay 6744') this.startPlayOrResumePlay(); }) } .width(CommonConstants.FULL_PERCENT) .height(CommonConstants.FULL_PERCENT) .justifyContent(FlexAlign.End) } .justifyContent(FlexAlign.End) .margin({ bottom: 8 }) } .backgroundImage(StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover) // .backgroundImage(this.isPuraWP() && this.currentSwiperIndex === 0 ? // (StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover) : this.imageLabelBg) .backgroundImageSize(this.isPuraWP() ? { width: '100%' } : { height: '150%', width: '100%' }) // .backgroundImagePosition(Alignment.Center) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .backgroundBlurStyle(this.isPuraWP() && this.currentSwiperIndex === 0 ? BlurStyle.NONE : BlurStyle.BACKGROUND_ULTRA_THICK) .hitTestBehavior(HitTestMode.Transparent) // .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }), // TransitionEffect.scale({ x: 0, y: 0 }) )) .backgroundBrightness({ rate: this.isPuraWP() ? 0.1 : 0, lightUpDegree: -0.1 }) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) .onClick(() => { // this.startAutoHide() if(this.isHiCar()) return if(this.is_auto_hide_progress){ this.is_auto_hide_progress = false } this.startAutoHide() }) } @State progressValue: number = 0; @State currentTime: string = "00:00"; @State totalTime: string = "00:00"; @State loadingVisible: Visibility = Visibility.None; @State replayVisible: Visibility = Visibility.None; @State slideEnable: boolean = false; @State aspRatio: number = 0.5; @State mContext: object | undefined = undefined; // private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; @State mFirst: boolean = true; @State mDestroyPage: boolean = false; @State playSpeed: number = 1; @State oldSeconds: number = 0; @State isSeekTo: boolean = false; @State isCurrentTime: boolean = false; @State videoWidth: string = '100%'; @State videoHeight: string = '100%'; @State initAspectRatio: number = 1; @State videoAspectRatio: number = this.initAspectRatio; private videoUrl: string = ''; private last: number = 0; @State videoParentAspectRatio: number = this.initAspectRatio; private mIjkMediaPlayer = IjkMediaPlayer.getInstance(); @State CONTROL_PlayStatus: number = PlayStatus.INIT; @State PROGRESS_MAX_VALUE: number = 100; @State updateProgressTimer: number = 0; @State isHide: boolean = false; //是否因此播放器的按钮,默认是进入横屏模式隐藏,点击视频的时候显示 @State name: string = ''; // @State loop: boolean = false; @State playType: number = 0; //0:连续播放 1:单片重复播放 2:正常播放 3:随机播放 @State multiple: string = '1.0X'; xcomponentController: XComponentController = new XComponentController() private panOptionBright: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }); private panOptionVolume: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical | PanDirection.Up | PanDirection.Down }); private panOptionSeekTo: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Horizontal }); //手势的音量控制和亮度控制 private positionX: number = PlayConstants.POSITION_X; private positionY: number = PlayConstants.POSITION_Y; private windowClass: window.Window = globalThis.windowClass @State volume: number = 0.5; @State volumeSmall: boolean = false @State autoParseMusicName: boolean = true @State isShowFileName: boolean = false @State isLongNameRoLL: boolean = true//长歌名滚动 @State volumeShow: boolean = PlayConstants.VOLUME_SHOW; @State bright: number = PlayConstants.BRIGHT; @State brightShow: boolean = PlayConstants.BRIGHT_SHOW; @State seekToShow: boolean = PlayConstants.BRIGHT_SHOW; @State screenWidth: number = 0 @State screenHeight: number = 0 @State statusBarHeight: number = 0 @State isYesFull: boolean = false private seekValue: number = 0 @State isVideoLock: boolean = false; // Whether the video playback is locked eventHub = getContext().eventHub; //投播组件 private avSessionController: AvSessionController = AvSessionController.getInstance(false); private castController: avSession.AVCastController | undefined = undefined; @State isCastPlaying: boolean = false; @State currentTime2: number = 0; @State currentStringTime: string = '00:00'; @State duration: number = 0; @State isReady: boolean = false; @State durationTime: number = 0; @State durationStringTime: string = '00:00'; @StorageLink('isPlaying') @Watch('animationRoFun') isPlaying: boolean = false; // @State isPlaying: boolean = false; private castSeek: boolean = false; private castItem: avSession.AVQueueItem | undefined = undefined; // @State isBgPlayOpen:boolean = true //是否启用后台播放 @State imageLabel: PixelMap | Resource = CommonConstants.musicBgList[0]; @State imageLabelBg: PixelMap | Resource = CommonConstants.musicBgList[0]; @State rotateAngle2: number = -9 @StorageLink('imageColor') imageColor: string = 'rgba(0, 0, 2, 1.00)'; @State cover: string | undefined = ''; @State artist: string | undefined = '' // 1.初始化controller private lyricController: LyricController = new LyricController() private lyricControllerXF: LyricController = new LyricController() //悬浮歌词控制器 private lyricControllerSingle: LyricController = new LyricController() //播放页单行歌词控制器 private parser = new LyricParser() @State randomColor: string = 'rgb(0,0,0)' @State randomShakenX: number = 0 @State randomShakenY: number = 0 @State lyricTextSize: number = 24 @State lyricContent: string = '' @State isDebug: boolean = false @State isHightLightCenter: boolean = true /** * 初始化歌词加载与展示逻辑 * @param lyricPath 歌词文件路径(支持普通.lrc和加密.lrcc) * @param isOnLineAndToast 是否弹出Toast提示(可选)获取在线歌词为true * @param isApi2 是否使用API2获取歌词(可选) * * 主要流程: * 1. 设置歌词显示样式(字号、颜色、行距、对齐、动画等)。 * 2. 优先尝试本地读取歌词文件(支持加密和普通两种后缀)。 * 3. 若本地无歌词且满足条件(调试模式或会员且安装时间超过2天),则联网获取歌词。 * 4. 获取到歌词后,解析并设置到lyricController,并保存到本地。 * 5. 本地有歌词时,读取文件内容(自动检测编码,支持解密),解析后设置到lyricController。 * 6. 解析失败或读取失败时,清空歌词显示。 * * 注意事项: * - 仅会员且安装时间超过2天才可联网获取歌词(调试模式isDebug=true时跳过限制)。 * - 支持加密歌词文件(.lrcc),自动检测并解密。 * - 歌词内容解析后通过lyricController驱动UI渲染。 */ async initLyric(lyricPath: string, isOnLineAndToast?: boolean, isApi2?: boolean, isLocal?: boolean) { if (StrUtil.isEmpty(lyricPath)) { return } this.initPipLyricSetting() this.lyricControllerSingle .setTextSize(15) .setCacheSize(4) .setTextColor("#FFFFFF") .setHighlightColor("#FFFFFF") .setHighlightScale(1.2) .setEmptyHint("") .setAlignMode('center') .setAnimationDuration(500) .setSingleLine(true) this.lyricContent = '' this.lyricController .setTextSize(this.currentLyricSize) .setCacheSize(4) .setTextColor("#DDDDDD") .setHighlightColor("#FFFFFF") .setEmptyHint("") .setAnimationDuration(1000) this.currentLyricAlignMode = PreferencesUtil.getNumberSync('LyricAlignMode', 1) this.blurDegree = PreferencesUtil.getNumberSync('setBlurDegree', 3) this.setBlurDegree(this.blurDegree, false) this.setLyricAlignMode(this.currentLyricAlignMode, false) this.setLyricTextSize(PreferencesUtil.getNumberSync('LyricTextSize', 18), false) this.setHighLyricTextSize(PreferencesUtil.getNumberSync('HighLyricTextSize', 1.23), false) this.lyricController.setLineSpace(PreferencesUtil.getNumberSync('LyricLineSpace', 10)) this.changeLyricColor(PreferencesUtil.getStringSync('LyricColor', '#FFFFFF'), false) this.changeLyricHightLightColor(PreferencesUtil.getStringSync('LyricHighLightColor', '#FFFFFF'), false) this.isHightLightCenter = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_SIMI, true) this.lyricController.setHightLightCenter(this.isHightLightCenter) this.lyricTextWeight = PreferencesUtil.getNumberSync('lyricTextWeight', 400) this.lyricController.setTextWeight(this.lyricTextWeight) this.showSingleLyric = false this.timeOffset = 0 this.lyricController.setLyric(null) this.lyricControllerSingle.setLyric(null) let jiaMilyricPath = lyricPath.substring(0, lyricPath.lastIndexOf('.')) + '.lrcc' console.log("onecold lyricPath =" + lyricPath) let neiqianLrc = '' let lyContent = this.currentSong?.lyricContent if(lyContent&&StrUtil.isNotEmpty(lyContent)){//取数据库里面的歌词lyContent neiqianLrc = lyContent } if (StrUtil.isNotEmpty(neiqianLrc) && !isOnLineAndToast&&!isLocal&&!isLocal) { console.log("onecold 找到内嵌歌词 neiqianLrc =" + neiqianLrc) //赋值给this.lyricContent,播控中心才可以显示歌词 this.lyricContent = neiqianLrc // 将文件内容按行分割成字符串数组 let lines = neiqianLrc.split('\n').map(line => line.trim()); // 3.解析歌词 let lyric = this.parser.parse(lines); // 4.设置歌词 this.lyricController.setLyric(lyric); this.lyricControllerXF.setLyric(lyric) this.lyricControllerSingle.setLyric(lyric) return } if (isOnLineAndToast || ((!FileUtil.accessSync(lyricPath) && !FileUtil.accessSync(jiaMilyricPath)) && !Utility.isVideoByExtension(this.videoUrl))) { if (this.currentSong !== undefined) { // ToastUtil.showShort('正在为你搜索在线歌词!') //判断是不是赞助会员,是会员的话才开启歌词功能 // if (!this.isDebug) { // if (!Utility.isNoble()) { // LogUtil.debug("onecold 不是赞助会员") // return // } // if(!Utility.isPassInstallTime(33)){ // LogUtil.debug("onecold not pass time") // // LogUtil.debug("onecold 用户安装app没超过12天" ) // return // } // } let artist = this.currentSong?.artist if (artist === undefined) { artist = '' } if (isApi2 === undefined) { isApi2 = false } NetAxiosUtil.getLyric(this.name, artist, isApi2).then((res) => { // LogUtil.debug("onecold res =" + res) if (StrUtil.isNotEmpty(res) && res !== 'unknown' && res !== 'Timeout was reached') { if (StrUtil.isNotEmpty(res)) { this.lyricContent = res let lines = res.split('\n').map(line => line.trim()); // 3.解析歌词 let lyric = this.parser.parse(lines); // 4.设置歌词 this.lyricController.setLyric(lyric); this.lyricControllerXF.setLyric(lyric) this.lyricControllerSingle.setLyric(lyric) this.showSingleLyric = true // 4.保存歌词文件lyric到本地 //2025年8月28日歌词不再加密下载 this.saveDataToFile(res, lyricPath, false) } else { this.lyricController.setLyric(null) this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) if (isOnLineAndToast) { ToastUtil.showShort('未获取到歌词!') } } } else { this.lyricController.setLyric(null) this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) if (isOnLineAndToast) { ToastUtil.showShort('未获取到歌词!') } } }) } else { this.lyricController.setLyric(null) this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) if (isOnLineAndToast) { ToastUtil.showShort('未获取到歌词!') } } return } try { let isJiaMi = false; let realLyricPath = lyricPath if (FileUtil.accessSync(jiaMilyricPath)) { isJiaMi = true realLyricPath = jiaMilyricPath console.info("onecold 找到加密本地歌词 "); } else { isJiaMi = false realLyricPath = lyricPath console.info("onecold 找到本地歌词 "); } // 3.读取文件内容并指定编码为 UTF-8 let file = fs.openSync(realLyricPath, fs.OpenMode.READ_ONLY); const stat = await fileIo.stat(file.fd); const arrayBuffer = new ArrayBuffer(stat.size); fs.read(file.fd, arrayBuffer) .then((readLen: number) => { console.info("read file data succeed"); // let buf = buffer.from(arrayBuffer, 0, readLen); let buf = new Uint8Array(arrayBuffer, 0, readLen); // 检查 buf 是否为空 if (buf.length === 0) { throw new Error("Buffer is empty after reading the file"); } try { // 检测文件编码 // let detectedEncoding = chardet.detect(buf); // console.info("onecoldT Detected encoding: " + detectedEncoding); let detectedEncoding = this.detect(arrayBuffer) console.info("onecoldT Detected encoding: " + detectedEncoding); // 使用检测到的编码解码,解决中文乱码问题 let textDecoder = new util.TextDecoder(detectedEncoding || 'utf-8'); console.info("onecoldT Detected textDecoder: " + textDecoder); let fileContent = textDecoder.decode(buf); console.info(`onecoldT The content of file1: ${fileContent}`); //解密 if (isJiaMi) { fileContent = StrUtil.unit8ArrayToStr(Base64Util.decodeSync(fileContent)) } console.info(`onecold The content of file2: ${fileContent}`); this.lyricContent = fileContent if(isLocal){//编辑标签选择本地歌词需要赋值给this.lyricConStr this.lyricConStr = fileContent } // 将文件内容按行分割成字符串数组 let lines = fileContent.split('\n').map(line => line.trim()); console.info(`onecold The content of file: ${fileContent}`); // 3.解析歌词 let lyric = this.parser.parse(lines); // 4.设置歌词 this.lyricController.setLyric(lyric); this.lyricControllerXF.setLyric(lyric) this.lyricControllerSingle.setLyric(lyric) this.showSingleLyric = true console.info(`The content of file: ${fileContent}`); } catch (chardetError) { console.error("chardet.detect failed with error: " + chardetError.message); this.lyricController.setLyric(null); this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) } }) .catch((err: BusinessError) => { this.lyricController.setLyric(null) this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) console.error("read file data failed with error message: " + err.message + ", error code: " + err.code); }) .catch((err: Error) => { this.lyricController.setLyric(null) this.lyricControllerXF.setLyric(null) this.lyricControllerSingle.setLyric(null) console.error("read file 2 data failed with error message: " + err.message); }) .finally(() => { fs.closeSync(file); }); } catch (error) { this.lyricController.setLyric(null) this.lyricControllerSingle.setLyric(null) this.lyricControllerXF.setLyric(null) Logger.error(TAG, 'init Lyric failed with err: ' + JSON.stringify(error)); } } detect(data: ArrayBuffer): string { //创建检测对象 let detector: UniversalDetector = new UniversalDetector(); // 传入检测数据并实时判断编码格式 detector.handleData(data, 0, data.byteLength); // 标记检测数据 读取结束 detector.dataEnd(); // 获取检测结果 let detected: string = detector.getDetectedCharset(); // 释放资源 detector.reset(); return detected; } //初始化悬浮歌词的自定义设置 initPipLyricSetting() { this.lyricControllerXF .setTextSize(this.currentLyricSize) .setCacheSize(4) .setTextColor("#DDDDDD") .setHighlightColor("#FFFFFF")// .setLineSpace(18) .setEmptyHint("") .setAnimationDuration(1000) this.currentLyricAlignModePip = PreferencesUtil.getNumberSync('LyricAlignModePip', 0) this.blurDegreePip = PreferencesUtil.getNumberSync('setBlurDegreePip', 3) this.setBlurDegree(this.blurDegreePip, true) this.setLyricAlignMode(this.currentLyricAlignModePip, true) this.setLyricTextSize(PreferencesUtil.getNumberSync('LyricTextSizePip', 18), true) this.setHighLyricTextSize(PreferencesUtil.getNumberSync('HighLyricTextSizePip', 1.23), true) this.lyricControllerXF.setLineSpace(PreferencesUtil.getNumberSync('LyricLineSpacePip', 10)) this.changeLyricColor(PreferencesUtil.getStringSync('LyricColorPip', '#FFFFFF'), true) this.changeLyricHightLightColor(PreferencesUtil.getStringSync('LyricHighLightColorPip', '#FFFFFF'), true) this.lyricControllerXF.setHightLightCenter(true) this.lyricTextWeightPip = PreferencesUtil.getNumberSync('lyricTextWeightPip', 400) this.lyricControllerXF.setTextWeight(this.lyricTextWeightPip) this.pipBg = PreferencesUtil.getStringSync('BGColorPip', '#FFFF54') this.pipBgIndex = PreferencesUtil.getNumberSync('pipBgIndex', 0) if (this.pipBgIndex === 0) { if (this.cover) { this.pipBg = this.cover } else { this.pipBg = this.imageColor } this.nodeController.updateBgColor(this.pipBg, this.lyricControllerXF) } } //根据item返回歌词内容 async getLyricContent(item: VideoItem): Promise { let neiqianLrc = '' let lyContent = item?.lyricContent if(lyContent&&StrUtil.isNotEmpty(lyContent)){ neiqianLrc = lyContent return neiqianLrc } try { //取数据库里面的歌词lyContent const lyricContent = await this.table.getLyricContentByFilePath(item.filePath); if (lyricContent&&StrUtil.isNotEmpty(lyricContent)) { console.log("onecold Lyrics:", lyricContent); neiqianLrc = lyricContent return neiqianLrc } } catch (error) { console.error("Error fetching lyrics:", error); } try { let filePath = item.filePath; let lyricPath = filePath.substring(0, filePath.lastIndexOf('.')) + '.lrc'; let jiaMilyricPath = lyricPath.substring(0, lyricPath.lastIndexOf('.')) + '.lrcc'; let isJiaMi = false; let realLyricPath = lyricPath; if (FileUtil.accessSync(jiaMilyricPath)) { isJiaMi = true; realLyricPath = jiaMilyricPath; console.info("onecold 找到加密本地歌词"); } else { console.info("onecold 找到本地歌词"); } let file = fs.openSync(realLyricPath, fs.OpenMode.READ_ONLY); const stat = await fileIo.stat(file.fd); const arrayBuffer = new ArrayBuffer(stat.size); const readLen = await fs.read(file.fd, arrayBuffer); console.info("read file data succeed"); let buf = new Uint8Array(arrayBuffer, 0, readLen); if (buf.length === 0) { throw new Error("Buffer is empty after reading the file"); } // let detectedEncoding = chardet.detect(buf); let detectedEncoding = this.detect(arrayBuffer) console.info("Detected encoding: " + detectedEncoding); let textDecoder = new util.TextDecoder(detectedEncoding || 'utf-8'); let fileContent = textDecoder.decode(buf); console.info(`onecold The content of file1: ${fileContent}`); if (isJiaMi) { fileContent = StrUtil.unit8ArrayToStr(Base64Util.decodeSync(fileContent)); } console.info(`onecold The content of file2: ${fileContent}`); fs.closeSync(file); return fileContent; } catch (error) { console.error('init Lyric failed with err: ' + JSON.stringify(error)); return ''; } } async saveDataToFile(content: string, filePath: string, isJiaMi?: boolean) { if (StrUtil.isEmpty(filePath)) { return } try { //将content进行加密 // let jiaMilyricPath = filePath.substring(0,filePath.lastIndexOf('.'))+'.lrcc' let lyricContent = '' if (isJiaMi) { lyricContent = Base64Util.encodeToStrSync(StrUtil.strToUint8Array(content)) } else { lyricContent = content } const file = await fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); // 清空文件内容 await fileIo.truncate(file.fd, 0); await fileIo.write(file.fd, lyricContent); await fileIo.close(file); console.log('onecold lyric file Data saved successfully.'); } catch (error) { console.error('onecold lyric file Failed to save data:', error); } } /** * Get largest proportion color of an image. */ getImageColor() { if (!this.context) { return; } let bg = Utility.getMusisBg() this.imageLabel = bg ColorConversion.setSysBarLightBackground(true); this.context.resourceManager.getMediaContent(bg) .then((value: Uint8Array) => { let buffer = value.buffer as ArrayBuffer; image.createImageSource(buffer).createPixelMap().then((pixelMap) => { effectKit.createColorPicker(pixelMap, (error, colorPicker) => { if (error) { Logger.error('Failed to create color picker.'); } else { let color = colorPicker.getLargestProportionColor(); let colorArr = ColorConversion.dealColor(color.red, color.green, color.blue); this.imageColor = `rgba(${colorArr[0]}, ${colorArr[1]}, ${colorArr[2]}, 1)`; } }) let headFilter = effectKit.createEffect(pixelMap); if (headFilter !== null) { headFilter.blur(15); headFilter.getEffectPixelMap().then((value) => { this.imageLabelBg = value; }) } }) .catch((error: BusinessError) => { Logger.error(`${error.code} + ${error.message}`) }) }) .catch((error: BusinessError) => { Logger.error(`${error.code} + ${error.message}`) }) } setImageColor(cover: string) { if (!this.context) { return; } if (cover === undefined) { return; } imagePathToPixelMap(cover).then((value) => { let headFilter = effectKit.createEffect(value); if (headFilter !== null) { headFilter.blur(15); headFilter.getEffectPixelMap().then((value) => { this.imageLabelBg = value; }) } }).catch((error: BusinessError) => { Logger.error(`${error.code} + ${error.message}`) }) } setImageColor2(cover: string) { if (!this.context) { return; } if (cover === undefined) { return; } LogUtils.getInstance().LOGI("setImageColor2--> cover ==" + cover); ImageUtils.getImageDealData(getContext(this), cover, this.videoUrl).then((data) => { this.imageColor = data.imageColor; if (data.blurPixelMap) { this.imageLabelBg = data.blurPixelMap; } }) } // 定义开始旋转的方法 // 定时器用于一百毫秒执行一次旋转角度 @State timer: number = 0 @State rotateAngle: number = 0 //封面旋转动画 animationRoFun() { if (this.isPlaying && !this.isCoverRectangle) { this.timer = setInterval(() => { this.rotateAngle += 1 }, 100) this.startRotation() } else { clearInterval(this.timer) this.stopRotation() } } // 定义开始旋转的方法 startRotation() { if (this.isPlaying) { animateTo({ duration: 777, iterations: 1, curve: Curve.Linear }, () => { this.rotateAngle2 = -9 }) } } // 回调,暂停和播放按钮回调,下一首和上一首 playChange() { // if(this.isCoverRectangle){ // return // } if (this.CONTROL_PlayStatus === PlayStatus.PLAY) { this.setIsPlaying(true) this.animationState = AnimationStatus.Running } else { this.setIsPlaying(false) this.animationState = AnimationStatus.Paused } } // 定义停止旋转的方法 stopRotation() { if (this.isCoverRectangle) { this.rotateAngle = 0 } else { //唱针以左上角为点选择逆时针40度 animateTo({ duration: 1000, curve: Curve.Linear, iterations: 1, }, () => { this.rotateAngle2 = -45; }); } } @Builder private LyricsTopItem() { Row() { Stack() { Image(StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover) .height(55) .width(55) .alt(this.imageLabel) .borderRadius(8) .clickEffect({ level: ClickEffectLevel.HEAVY }) .margin({ left: this.currentLyricAlignMode === 0 ? 28 : -15 }) } .width('18%') Column() { Column() { Text(this.name) .fontSize(16) .maxLines(1) .fontWeight(FontWeight.Bolder) .fontColor(this.currentLyricColor) .margin({ left: this.currentLyricAlignMode === 0 ? 18 : 0 }) Row() { Text(this.currentSong?.artist !== undefined ? this.currentSong?.artist : '') .fontSize(13) .fontWeight(FontWeight.Bold) .padding({ top: 8 }) .fontColor(this.currentLyricColor) .margin({ left: this.currentLyricAlignMode === 0 ? 18 : 0 }) Blank() } } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) } .height('100%') .width('65%') Column() { Image($r('app.media.lyric')) .width(28) .aspectRatio(CommonConstants.ASPECT_RATIO) .margin({ right: 30 }) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .onClick(() => { this.isLyricSetting = !this.isLyricSetting; }) .bindSheet($$this.isLyricSetting, this.lyricSettingSheet(false), { height: '95%', dragBar: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, showClose: true, blurStyle: BlurStyle.Thin, backgroundColor: Color.Transparent, // title: { title: $r('app.string.lyric_setting') } }) } .width('18%') } .width('82%') .height(58) .justifyContent(FlexAlign.SpaceBetween) } //歌词显示 @Builder PlayerLyrics() { Column() { Column() { this.LyricsTopItem() } .height(this.isPuraWP() ? 15 : 58) .visibility(this.currentBreakpoint !== BreakpointTypeEnum.SM ? Visibility.None : Visibility.Visible) .margin({ left: 6, top: this.isPuraWP() ? 5 : 38 }) .width("100%") Column() { LyricView2({ controller: this.lyricController, enableSeek: true, seekUIColor: "#ff0000", // 滑动定位的按钮和文本颜色 seekLineColor: "#80ffffff", // 滑动定位线颜色 seekUIStyle: "listItem", // 滑动定位样式(seekLine传统样式,listItem类似抖音汽水音乐样式) onSeekAction: (position: number) => { // 滑动歌词触发seek定位回调 if (!this.isPlaying) { console.info('onecold startPlayOrResumePlay 7527') this.startPlayOrResumePlay() } else { this.isSeekTo = true; this.mDestroyPage = false; this.showLoadIng(); this.seekTo(position + ""); this.isSeekTo = false; } return true } }) .width("100%") .layoutWeight(1) .margin({ left: this.currentLyricAlignMode === 0 ? 0 : 50, top: 2, bottom: this.isPhoneLan()||this.isHiCarKuanBianPing() ? 40 : 10 }) if (this.isPhoneLan()) { Column() { this.playProgressView() // this.playCenterView() } .margin({ bottom: 30, right: this.currentLyricAlignMode == 0 ? 0 : 30 }) } } .position({ top: this.currentBreakpoint !== BreakpointTypeEnum.SM ? (this.isPhoneLan() ? 0 : 45) : (this.isPuraWP() ? 58 : 99) }) .justifyContent(FlexAlign.Center) .height(this.isCoverOpacity() ? this.isPhoneLan()?'95%':'90%' : '82%') } } @Builder SingleLyricView() { Column() { Column() { LyricView2({ controller: this.lyricControllerSingle, enableSeek: false, seekUIColor: "#ff0000", // 滑动定位的按钮和文本颜色 seekLineColor: "#80ffffff", // 滑动定位线颜色 seekUIStyle: "listItem", // 滑动定位样式(seekLine传统样式,listItem类似抖音汽水音乐样式) }) } .height(66) .margin({ bottom: 20 }) } } //播放器的底部控制中心,上一首,暂停,下一首,播放进度条等 @Builder BottomControl() { Column() { this.TopPlayProgressView() //播放 暂停,下一首,上一首 this.playCenterView() //播放进度条 this.BottomView() } .position({ bottom: this.isHiCarSmall()?50:(this.isCoverOpacity() ? 55 : 70) }) // 将 固定在底部 } //播放控制上一首 下一首 暂停和播放 @Builder playCenterView(){ Row() { Row({ space: 20 }) { Button({type:ButtonType.Circle,stateEffect:true}){ //更多功能 Image($r('app.media.menu')) .width(24) .clickEffect({level:ClickEffectLevel.MIDDLE}) .bindSheet($$this.isShowMoreView, this.PlayMoreSheet(), { height: this.isCoverOpacity() ? '95%' : '93%', preferType: this.currentBreakpoint!==BreakpointTypeEnum.SM?SheetType.CENTER:SheetType.BOTTOM, dragBar: true, showClose: true, blurStyle: BlurStyle.Thin, backgroundColor: Color.Transparent, }) } .margin({ left: 5 }) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .backgroundColor(Color.Transparent) .onClick(() => { this.isShowMoreView = !this.isShowMoreView; }) Button({type:ButtonType.Circle,stateEffect:true}){ Image($r('app.media.ic_previous')) .width(33) .clickEffect({level:ClickEffectLevel.HEAVY}) .aspectRatio(CommonConstants.ASPECT_RATIO) } .onClick(async () => { this.playPrevious() }) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .backgroundColor(Color.Transparent) .margin({ left: 5 }) } Button({type:ButtonType.Circle,stateEffect:true}){ Column() { Image(this.CONTROL_PlayStatus === PlayStatus.PLAY ? (this.isCircleBtn?$r('app.media.hm_pause'):$r('app.media.ic_public_play')) : (this.isCircleBtn?$r('app.media.hm_play2'):$r('app.media.ic_public_pause'))) .width(this.isPhoneLan()?45:55) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .fillColor(Color.White) .aspectRatio(CommonConstants.ASPECT_RATIO) .onClick(async () => { console.info('onecold playOrPause 7651') this.playOrPause() }) } } .backgroundColor(Color.Transparent) .layoutWeight(1) // .margin({ left: 38, right: 38 }) Row({ space: 20 }) { Button({ type: ButtonType.Capsule, stateEffect: true }) { Image($r('app.media.ic_next')) .width(33) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .aspectRatio(CommonConstants.ASPECT_RATIO) .onClick(() => { this.playNext(); }) } .backgroundColor(Color.Transparent) .margin({ right: 5 }) .width(33) if(this.isPhoneLan()){ this.fullButton() }else{ Button({type:ButtonType.Circle,stateEffect:true}){ //添加或取消收藏 SymbolGlyph(Utility.getIsFav(this.favList,this.currentSong)? $r('sys.symbol.heart_fill'):$r('sys.symbol.heart')) .fontSize(23) .fontColor([Color.White]) .alignSelf(ItemAlign.Center) // .width(24) .clickEffect({level:ClickEffectLevel.MIDDLE}) .aspectRatio(CommonConstants.ASPECT_RATIO) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .onClick(async () => { if(this.currentSong){ this.doFav(this.currentSong) } }) } .backgroundColor(Color.Transparent) .width(24) } } // .margin({ left: 5 }) } .width('95%') .margin({ bottom:this.isCoverOpacity()?0:20 }) .justifyContent(FlexAlign.SpaceEvenly) } @Builder TopPlayProgressView(){ Row() { Column(){ Row(){ Stack(){ SymbolGlyph($r('sys.symbol.arrow_counterclockwise')) .fontColor([Color.White]) .fontSize(21) .effectStrategy(1) Text(this.fastForwardSeconds) .fontColor(Color.White) .fontSize(10) .fontWeight(FontWeight.Bold) } .margin({left:12,bottom:6}) .visibility(this.isShowBackFast?Visibility.Visible:Visibility.None) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(()=>{ this.backForward() }) Slider({ value: this.progressValue, min: 0, max: this.PROGRESS_MAX_VALUE, step: 1, style: SliderStyle.InSet }) .height(20) .blockColor('rgba(255,255,255,1)') .trackColor('rgba(255,255,255,0.3)') .selectedColor(Color.White) .trackThickness(4) .layoutWeight(1) .showSteps(false) .showTips(true) .alignSelf(ItemAlign.Center) .enabled(this.slideEnable) .onChange((value: number, mode: SliderChangeMode) => { // if (mode == 2) { this.isSeekTo = true; this.mDestroyPage = false; this.showLoadIng(); LogUtils.getInstance().LOGI("slider-->seekValue start:" + value); let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100); this.seekTo(seekValue + ""); // this.setProgress() LogUtils.getInstance().LOGI("slider-->seekValue end:" + seekValue); this.isSeekTo = false; // } }) Stack(){ SymbolGlyph($r('sys.symbol.arrow_clockwise')) .fontColor([Color.White]) .fontSize(21) .effectStrategy(1) Text(this.fastForwardSeconds) .fontColor(Color.White) .fontSize(10) .fontWeight(FontWeight.Bold) } .margin({right:10,bottom:6}) .visibility(this.isShowBackFast?Visibility.Visible:Visibility.None) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(()=>{ this.fastForward() }) } .padding({ right: 7,left:22 }) Row(){ Text(this.currentTime) .fontSize(10) .fontColor(Color.White) Blank() .layoutWeight(1) Text(this.totalTime) .fontSize(10) .fontColor(Color.White) } .justifyContent(FlexAlign.Start) .padding({ right: 18,left:36 }) } } //x5的竖屏模式需要99% .width(this.isLandscape?'80%':'93%') .justifyContent(FlexAlign.Center) .margin({bottom:6}) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) } //播放进度条 @Builder BottomView(){ Row() { Button({type:ButtonType.Circle,stateEffect:true}){ Column() { if (this.playType === 0) { Image($r('app.media.loop')) .width(27) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 1) { Image($r('app.media.single')) .width(27) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 2) { Image($r('app.media.normal_play')) .width(27) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 3) { Image($r('app.media.random')) .width(27) .aspectRatio(CommonConstants.ASPECT_RATIO) } } } .layoutWeight(1) .margin({ left: 5 }) .backgroundColor(Color.Transparent) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .onClick(async () => { this.setLoopMode() }) this.fullButton() Button({type:ButtonType.Circle,stateEffect:true}){ Column() { Image($r('app.media.lyric')) .width(26) .aspectRatio(CommonConstants.ASPECT_RATIO) } } .layoutWeight(1) .backgroundColor(Color.Transparent) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .onClick(async () => { this.isLyricSetting = !this.isLyricSetting; }) //播放列表 Button({type:ButtonType.Circle,stateEffect:true}){ Image($r('app.media.hm_playlist')) .width(24) .fillColor(Color.White) .aspectRatio(CommonConstants.ASPECT_RATIO) .bindSheet($$this.isShowSheetView, this.PlayListSheet(), { height: '95%', dragBar: true, showClose: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, blurStyle: BlurStyle.Thin, backgroundColor: this.isPlayListBgGrass?Color.Transparent:$r('app.color.silvery'), title: { title: Utility.resourceToString(this.context, $r('app.string.current_play_list')) + `(${this.songList.length}首)` } }) } .layoutWeight(1) .backgroundColor(Color.Transparent) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .onClick(() => { this.isShowSheetView = !this.isShowSheetView; if(this.isPlayListBgGrass){ this.isFrontWhite = true }else{ this.isFrontWhite = false } this.scrollIndex() }) } .width(this.isLandscape?'88%':'95%') .justifyContent(FlexAlign.SpaceAround) .height(30) .margin({top:3}) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) } @Builder fullButton(){ Button({type:ButtonType.Circle,stateEffect:true}){ Column() { Image($r('app.media.full_4')) .width(25) .aspectRatio(CommonConstants.ASPECT_RATIO) } } .layoutWeight(1) .backgroundColor(Color.Transparent) .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5}) .onClick(async () => { if (this.isLandscape) { this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT); } else { this.setOrientation(window.Orientation.USER_ROTATION_LANDSCAPE); } }) } //旧版本播放进度条 @Builder playProgressView() { Row() { Button({ type: ButtonType.Circle, stateEffect: true }) { Column() { if (this.playType === 0) { Image($r('app.media.loop')) .width($r('app.float.control_image_width')) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 1) { Image($r('app.media.single')) .width($r('app.float.control_image_width')) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 2) { Image($r('app.media.normal_play')) .width($r('app.float.control_image_width')) .aspectRatio(CommonConstants.ASPECT_RATIO) } else if (this.playType === 3) { Image($r('app.media.random')) .width($r('app.float.control_image_width')) .aspectRatio(CommonConstants.ASPECT_RATIO) } } } .visibility(this.is_auto_hide_progress ? Visibility.None : Visibility.Visible) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .backgroundColor(Color.Transparent) .margin({ right: 10 }) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .onClick(async () => { this.setLoopMode() }) Text(this.currentTime) .fontSize($r('app.float.slider_font_size')) .fontColor(Color.White) .margin(3) Slider({ value: this.progressValue, min: 0, max: this.PROGRESS_MAX_VALUE, step: 1, style: SliderStyle.OutSet }) .width('600px') .blockColor('rgba(255,255,255,1)') .trackColor('rgba(255,255,255,0.3)') .selectedColor(Color.White) .trackThickness(PlayConstants.PROGRESS_TRACK_THICKNESS) .layoutWeight(1) .margin({ left: PlayConstants.PROGRESS_MARGIN_LEFT }) .showSteps(false) .showTips(true) .enabled(this.slideEnable) .onChange((value: number, mode: SliderChangeMode) => { // if (mode == 2) { this.isSeekTo = true; this.mDestroyPage = false; this.showLoadIng(); LogUtils.getInstance().LOGI("slider-->seekValue start:" + value); let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100); this.seekTo(seekValue + ""); // this.setProgress() LogUtils.getInstance().LOGI("slider-->seekValue end:" + seekValue); this.isSeekTo = false; // } }) Text(this.totalTime) .fontSize($r('app.float.slider_font_size')) .fontColor(Color.White) .margin(3) .margin({ left: PlayConstants.PROGRESS_MARGIN_LEFT }) //播放列表 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($r('app.media.hm_playlist')) .width($r('app.float.control_image_width')) .fillColor(Color.White) .aspectRatio(CommonConstants.ASPECT_RATIO) .bindSheet($$this.isShowSheetView, this.PlayListSheet(), { height: '95%', dragBar: true, showClose: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, blurStyle: BlurStyle.Thin, backgroundColor: this.isPlayListBgGrass ? Color.Transparent : $r('app.color.silvery'), title: { title: Utility.resourceToString(this.context, $r('app.string.current_play_list')) + `(${this.songList.length}首)` } }) } .visibility( this.is_auto_hide_progress ? Visibility.None : Visibility.Visible) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .backgroundColor(Color.Transparent) .margin({ right: 20, left: 10 }) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .onClick(() => { this.isShowSheetView = !this.isShowSheetView; if (this.isPlayListBgGrass) { this.isFrontWhite = true } else { this.isFrontWhite = false } this.scrollIndex() }) } //x5的竖屏模式需要99% .width(this.isPhonePortrait() ? '93.4%' : this.currentHeightBreakpoint == 1 && this.currentWidthBreakpoint == 2 ? '99%' : '82%') .height(this.isPhoneLan() ? 25 : 33) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) } isBigScreen() { if (this.currentWidthBreakpoint == WidthBreakpoint.WIDTH_LG) { return true } if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE && this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM && this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG) { return false } else { return true } return false } @State is_auto_hide_progress: boolean = false //手机横屏自动隐藏播放进退条,点击屏幕可以显示,倒计时4秒后又自动隐藏 @State isCoverRectangle: boolean = false @State isCoverTop: boolean = true // 添加控制缩放的状态变量 @State scaleValueImage: number = 1 @Builder CoverInfo() { Column() { //如果是方形,判断是不是封面在顶部,如果不是顶部,显示musicNameInfo, 如果是圆形显示musicNameInfo if (this.isCoverRectangle) { if (!this.isCoverTop) { this.musicNameInfo(false) } } else { this.musicNameInfo(false) } Stack() { // 唱片旋转效果 Image($r('app.media.ic_music_disc')) .width(245) .height(245) .margin({ right: 20, left: 20 ,top:this.isCoverRectangle||this.isLandscape?0:43}) .aspectRatio(1) .opacity(this.isCoverOpacity() || this.isCoverRectangle ? 0 : 1) .visibility(this.isCoverOpacity() || this.isCoverRectangle ? Visibility.None : Visibility.Visible) .borderRadius('100%') .align(Alignment.Center) .clip(true)// .rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle }) .rotate({ angle: this.rotateAngle, centerX: "50%", centerY: "50%", }) .animation({ duration: 100, curve: Curve.Linear }) .shadow({ radius: 30, color: this.randomColor }) Column() { if (this.isCoverTop && !this.isCoverOpacity() && this.isCoverRectangle) { Image(StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover) .height(this.isHiCar()?(px2vp(this.windowHeight)*0.46):'auto') .width(this.isHiCar()?'auto':this.isBigScreen() ? '72%' : this.isCoverTopBig ? '100%' : '88%') .objectFit(this.isCoverRectangle ? ImageFit.Contain : ImageFit.Auto) .alt(this.imageLabel) .margin({ right: 8, left: 8, top: 8 }) .aspectRatio(1) .opacity(this.opacityValueImage) .scale({ x: this.scaleValueImage, y: this.scaleValueImage }) // 添加缩放效果 .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.8 }) .visibility(this.isPuraWP() ? Visibility.None : Visibility.Visible) .borderRadius(this.isCoverRectangle ? 20 : '100%') .align(Alignment.Center)// .clip(true) .rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle }) .onClick(() => { this.getImageColor() }) this.musicNameInfo(false) } else { Image(StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover) .height(this.isHiCarSmall()?(this.isHiCarKuanBianPing()?140:px2vp(this.windowHeight)*0.38): (this.isCoverOpacity() ? (px2vp(this.windowHeight)*0.62) : this.isCoverRectangle ? 320 : 162)) .objectFit(this.isCoverRectangle ? ImageFit.Contain : ImageFit.Auto) .alt(this.imageLabel) .margin({ right: 20, left: 20,top:this.isCoverRectangle||this.isLandscape?0:43 }) .aspectRatio(1) .opacity(this.opacityValueImage) .scale({ x: this.scaleValueImage, y: this.scaleValueImage }) // 添加缩放效果 .visibility(this.isPuraWP() ? Visibility.None : Visibility.Visible) .borderRadius(this.isCoverRectangle ? 20 : '100%') .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.8 }) .align(Alignment.Center)// .clip(true) .rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle }) .onClick(() => { this.getImageColor() }) if (this.isPhoneLan()) { Text(this.name) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ top: 13 }) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(1) .width('99%') .textAlign(TextAlign.Center) .fontColor(Color.White) } } } // 唱针以左上角为点选择逆时针40度 Image($r('app.media.ic_music_cover_hand')) .height(130) .margin({ bottom: 220, top: 0 }) .align(Alignment.Top) .rotate({ angle: this.rotateAngle2, centerX: 0, centerY: 0 }) .opacity(this.isCoverOpacity() || this.isCoverRectangle ? 0 : 1) .visibility(this.isCoverOpacity() || this.isCoverRectangle ? Visibility.None : Visibility.Visible) Column({ space: 8 }) { Text(this.name) .fontSize(this.isPuraWP() ? 24 : (this.name.length >= 28 || this.isCoverOpacity() ? 18 : 22)) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .maxLines(1) .width('48%') .textAlign(TextAlign.Center) .fontColor(Color.White) .visibility(this.isPhoneLan() ? Visibility.None : Visibility.Visible) Text(this.artist) .fontSize(this.isPuraWP() ? 18 : (this.isCoverOpacity() ? 12 : 15)) .fontColor(Color.White) .fontWeight(FontWeight.Bold) .visibility(this.isPhoneLan() ? Visibility.None : Visibility.Visible) } .justifyContent(FlexAlign.Start) .zIndex(1) .visibility(this.isCoverOpacity() ? Visibility.Visible : Visibility.None) } .margin({ bottom: this.isCoverOpacity() || this.isCoverRectangle ? (this.isPhonePortrait() && this.isShowSingleLineLyric ? 20 : 55) : 0, top: this.isCoverOpacity() ? 0 : 20 }) if (this.isPhonePortrait() && this.isShowSingleLineLyric) { this.SingleLyricView() } if (this.isPhoneLan()) { Column() { this.playCenterView() // this.playProgressView() } .position({ left: 28, bottom: 38 }) } else { this.BottomControl() } } .width(CommonConstants.FULL_PERCENT) .clip(true) } @Builder private musicNameInfo(isHidden: boolean) { Column({ space: 8 }) { Text(this.name) .fontSize(this.name.length >= 28 || this.isCoverOpacity() ? 18 : 22) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .maxLines(1) .width('99%') .textAlign(TextAlign.Center) .fontColor(Color.White) Text(this.artist + ' ' + this.currentSong?.album) .fontSize(this.isCoverOpacity() ? 12 : 15) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .fontColor(Color.White) .textAlign(TextAlign.Center) .visibility(StrUtil.isEmpty(this.artist)?Visibility.None:Visibility.Visible) .width('99%') .maxLines(1) } .margin({ top: this.isCoverOpacity() ? 20 : isHidden ? 40 : 20 }) .zIndex(1) .scale({ x: this.scaleValueImage, y: this.scaleValueImage }) // 添加缩放效果 .visibility(this.isCoverOpacity() || isHidden ? Visibility.None : Visibility.Visible) } @Builder private PlayTitle() { Row() { Row() { Image($r('app.media.ic_back_down')) .width($r('app.float.title_image_size'))// .aspectRatio(CommonConstants.ASPECT_RATIO) .hitTestBehavior(HitTestMode.Transparent) Text(this.name + ' ' + this.artist) .fontColor(Color.White) .fontSize($r('app.float.title_font_size')) .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动 .maxLines(1) .visibility(Visibility.None) .margin({ left: 2, right: 30 }) } .layoutWeight(1) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .visibility(this.isHide ? Visibility.Hidden : Visibility.Visible) .onClick(() => { this.isShowPlay = false; }) Column() { AVCastPicker({ normalColor: Color.White, onStateChange: this.onStateVastChange }) .width(24) .height(24) } .visibility(this.isHide ? Visibility.Hidden : Visibility.None) } .width(PlayConstants.ROW_WIDTH) } @Builder lyricSettingSheet(isPip: boolean) { Scroll() { this.lyricSetting(isPip) } .width('100%') .height('100%') } @Builder lyricSetting(isPip: boolean) { Column() { // 时间调节模块 if (!isPip) { this.BuildTimeControls() } // 字号控制模块 this.BuildFontControls(isPip) } } // 歌词时间偏移状态 @State timeOffset: number = 0.0 @State currentLyricColor: string = '#FFFFFF' @State currentLyricColorPip: string = '#FFFFFF' @State currentHighLightLyricColor: string = '#FF4081' @State currentHighLightLyricColorPip: string = '#FF4081' // 歌词字号状态 @State currentLyricSize: number = 20 @State currentLyricSizePip: number = 20 // 高亮歌词放大倍数 @State currentHightLyricSize: number = 1.2 @State currentHightLyricSizePip: number = 1.2 @State currentLyricLineSpace: number = 10 @State currentLyricLineSpacePip: number = 10 @State currentLyricAlignMode: number = 1 @State currentLyricAlignModePip: number = 1 @State blurDegree: number = 2 @State blurDegreePip: number = 2 @State isShowSelectColor: boolean = false @State isShowHLSelectColor: boolean = false @State isShowPipBgSelectColor: boolean = false @State pipLyWidth: number = 330 @State pipLyHeight: number = 120 // 构建颜色选择器 @Builder SelectColor(isHighColor: boolean, isPip: boolean, isBGPip?: boolean) { Row({ space: 10 }) { HSBColorPicker({ color: '#FFFFFF', radius: 8, layout: HSBColorPickerLayout.COLUMN, predefine: ['#8b27f4', '#73f9fc', '#fffe55', '#f5cee3', '#eb4827', '#e93bf4', '#3e68f4', '#c5e6d3', '#e4e4e4', '#fa7105'], onChange: (value: string) => isHighColor ? this.changeLyricHightLightColor(value, isPip) : this.changeLyricColor(value, isPip, isBGPip) }) .height(250) .layoutWeight(1) .padding(25) } .width('66%') } @State isOpenPip: boolean = false @State pipBgIndex: number = 0 // 构建字号控制器 @Builder BuildFontControls(isPip: boolean) { Column() { if (isPip) { Column() { Row() { Text('悬浮歌词') .margin({ left: 18 }) .fontSize(15) .fontColor(Color.White) .fontWeight(480) .layoutWeight(1) Toggle({ type: ToggleType.Switch, isOn: this.isOpenPip }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ right: 18 }) .onChange((checked: boolean) => { this.isOpenPip = checked; if (this.isOpenPip) { this.startPip() } else { this.stopPip() } PreferencesUtil.put('isOpenPip', this.isOpenPip) }) .width(50) .height(30); } .height(55) } .backgroundColor(Color.Transparent) .borderRadius(15) .margin({ left: 30, right: 30, top: 0, bottom: 20 }) .padding(0) .border({ color: Color.White, width: 1.8 }) }else{ Column() { Row() { Text('单行歌词') .margin({ left: 18 }) .fontSize(15) .fontColor(Color.White) .fontWeight(480) .layoutWeight(1) Toggle({ type: ToggleType.Switch, isOn: this.isShowSingleLineLyric }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ right: 18 }) .onChange((checked: boolean) => { this.isShowSingleLineLyric = checked; PreferencesUtil.put(SettingPage.IS_SHOW_SLLYRIC, this.isShowSingleLineLyric) }) .width(50) .height(30); } .height(55) } .backgroundColor(Color.Transparent) .borderRadius(15) .margin({ left: 30, right: 30, top: 10, bottom: 10 }) .padding(0) .border({ color: Color.White, width: 1.8 }) } Row() { Text(`歌词居中:`) .fontSize(14) .fontColor(Color.White) .margin({ left: 2 }) Row() { ForEach(['居中', '居左'], (size: string, index: number) => { Button(size) .width(60) .height(28) .fontSize(12) .fontColor('#FFFFFF') .backgroundColor((!isPip && this.currentLyricAlignMode === index) || (isPip && this.currentLyricAlignModePip === index) ? this.themeColor : Color.Transparent) .border({ color: (!isPip && this.currentLyricAlignMode === index) || (isPip && this.currentLyricAlignModePip === index) ? '#007DFF' : '#DDDDDD', width: 1.8 }) .onClick(() => { this.setLyricAlignMode(index, isPip) }) .margin({ left: 10, right: 8 }) }) } .width('76%') Blank() } .margin({ top: 10, bottom: 10, right: 20, left: 20 }) if (isPip) { Row() { Text(`悬浮背景:`) .fontSize(14) .fontColor(Color.White) Select([ { value: CommonConstants.PIP_LYRIC_BG[0] }, { value: CommonConstants.PIP_LYRIC_BG[1] }]) .font({ size: 13, weight: FontWeight.Medium }) .fontColor(Color.White) .margin({ left: 10 }) .selected(this.pipBgIndex) .value(CommonConstants.PIP_LYRIC_BG[this.pipBgIndex]) .onSelect(async (_index: number, text?: string | undefined) => { this.pipBgIndex = _index PreferencesUtil.put('pipBgIndex', this.pipBgIndex) if (this.pipBgIndex === 0) { if (this.cover) { this.pipBg = this.cover } else { this.pipBg = this.imageColor } this.nodeController.updateBgColor(this.pipBg, this.lyricControllerXF) } else { this.pipBg = PreferencesUtil.getStringSync('BGColorPip', '#FFFF54') } }) Column() { } .backgroundColor(this.pipBg) .margin({ left: 10, right: 10 }) .borderRadius(20) .height(28) .width(28) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.paintbrush')) .fontColor([Color.White]) .effectStrategy(1) Text(`选择颜色`) .fontSize(13) .margin({ left: 10 }) .fontColor(Color.White) } } .visibility(this.pipBgIndex === 1 ? Visibility.Visible : Visibility.None) .backgroundColor(Color.Transparent) .border({ color: '#FFFFFF', radius: 20, width: 1.8 }) .height(36) .width(110) .onClick(() => { this.isShowPipBgSelectColor = !this.isShowPipBgSelectColor }) .bindPopup(this.isShowPipBgSelectColor, { builder: this.SelectColor(false, isPip, true), placement: Placement.Top, mask: { color: '#33000000' }, enableArrow: false, //是否显示箭头 showInSubWindow: false, onStateChange: (e) => { if (!e.isVisible) { this.isShowPipBgSelectColor = false } } }) } .width('76%') .margin({ bottom: 5, right: 45, left: 20 }) } if (!isPip) { Row() { Text(`高亮居中:`) .fontSize(14) .fontColor(Color.White) Column() { Toggle({ type: ToggleType.Switch, isOn: this.isHightLightCenter }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .onChange((checked: boolean) => { this.isHightLightCenter = checked; PreferencesUtil.put(SettingPage.IS_SHOW_SIMI, this.isHightLightCenter) this.lyricController.setHightLightCenter(this.isHightLightCenter) }) .width(50) .height(30) .alignSelf(ItemAlign.Start) .margin({ left: 12 }) Blank() } .width('76%') } .margin({ left: 20, right: 15, top: 10, bottom: 10 }) } Row() { Text(`歌词颜色:`) .fontSize(14) .fontColor(Color.White) Text(isPip ? this.currentLyricColorPip : this.currentLyricColor) .fontSize(15) .fontColor(Color.White) .margin({ left: 12, right: 12 }) Column() { } .backgroundColor(isPip ? this.currentLyricColorPip : this.currentLyricColor) .margin({ right: 12 }) .borderRadius(20) .height(28) .width(28) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.paintbrush')) .fontColor([Color.White]) .effectStrategy(1) Text(`选择颜色`) .fontSize(13) .margin({ left: 10 }) .fontColor(Color.White) } } .backgroundColor(Color.Transparent) .border({ color: '#FFFFFF', radius: 20, width: 1.8 }) .height(36) .width(110) .onClick(() => { this.isShowSelectColor = !this.isShowSelectColor }) .bindPopup(this.isShowSelectColor, { builder: this.SelectColor(false, isPip), placement: Placement.Top, mask: { color: '#33000000' }, enableArrow: false, //是否显示箭头 showInSubWindow: false, onStateChange: (e) => { if (!e.isVisible) { this.isShowSelectColor = false } } }) } .width('76%') .margin({ top: 10, bottom: 10, right: 45, left: 20 }) Row() { Text(`高亮颜色:`) .fontSize(14) .fontColor(Color.White) Text(isPip ? this.currentHighLightLyricColorPip : this.currentHighLightLyricColor) .fontSize(15) .fontColor(Color.White) .margin({ left: 12, right: 12 }) Column() { } .backgroundColor(isPip ? this.currentHighLightLyricColorPip : this.currentHighLightLyricColor) .margin({ right: 12 }) .borderRadius(20) .height(28) .width(28) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.paintbrush')) .fontColor([Color.White]) .effectStrategy(1) Text(`选择颜色`) .fontSize(13) .margin({ left: 10 }) .fontColor(Color.White) } } .backgroundColor(Color.Transparent) .border({ color: '#FFFFFF', radius: 20, width: 1.8 }) .height(36) .width(110) .onClick(() => { this.isShowHLSelectColor = !this.isShowHLSelectColor }) .bindPopup(this.isShowHLSelectColor, { builder: this.SelectColor(true, isPip), placement: Placement.Top, mask: { color: '#33000000' }, enableArrow: false, //是否显示箭头 showInSubWindow: false, onStateChange: (e) => { if (!e.isVisible) { this.isShowHLSelectColor = false } } }) } .width('76%') .margin({ top: 10, bottom: 10, right: 45, left: 20 }) if (isPip) { Row() { Text(`显示宽度:`) .fontSize(14) .fontColor(Color.White) Slider({ value: this.pipLyWidth, min: 10, max: 800, step: 1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.pipLyWidth = value if (this.pipController) { this.pipController.updateContentSize(this.pipLyWidth, this.pipLyHeight); PreferencesUtil.putSync('pipLyWidth', this.pipLyWidth) } }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) Row() { Text(`显示高度:`) .fontSize(14) .fontColor(Color.White) Slider({ value: this.pipLyHeight, min: 10, max: 1000, step: 1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.pipLyHeight = value if (this.pipController) { this.pipController.updateContentSize(this.pipLyWidth, this.pipLyHeight); PreferencesUtil.putSync('pipLyHeight', this.pipLyHeight) } }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) } Row() { Text(`歌词字号:`) .fontSize(14) .fontColor(Color.White) Slider({ value: isPip ? this.currentLyricSizePip : this.currentLyricSize, min: 12, max: 30, step: 0.1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.setLyricTextSize(value, isPip) }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) Row() { Text(`高亮倍数:`) .fontSize(14) .fontColor(Color.White) Slider({ value: isPip ? this.currentHightLyricSizePip : this.currentHightLyricSize, min: 1, max: 2, step: 0.1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.setHighLyricTextSize(value, isPip) }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) Row() { Text(`歌词间隙:`) .fontSize(14) .fontColor(Color.White) Slider({ value: isPip ? this.currentLyricLineSpacePip : this.currentLyricLineSpace, min: 0, max: 100, step: 1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.setLyricLineSpace(value, isPip) }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) //取值间隔为100,默认为400,取值越大,字体越粗 Row() { Text(`歌词字重:`) .fontSize(14) .fontColor(Color.White) Slider({ value: isPip ? this.lyricTextWeightPip : this.lyricTextWeight, min: 100, max: 900, step: 100, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { if (isPip) { this.lyricTextWeightPip = value this.lyricControllerXF.setTextWeight(this.lyricTextWeightPip) PreferencesUtil.putSync('lyricTextWeightPip', this.lyricTextWeightPip) } else { this.lyricTextWeight = value this.lyricController.setTextWeight(this.lyricTextWeight) PreferencesUtil.putSync('lyricTextWeight', this.lyricTextWeight) } }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 5 }) if (!isPip) { Row() { Text(`歌词模糊:`) .fontSize(14) .fontColor(Color.White) Slider({ value: isPip ? this.blurDegreePip : this.blurDegree, min: 0, max: 5, step: 0.1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(6) .onChange((value: number) => { this.setBlurDegree(value, isPip) }) .width('76%') } .margin({ left: 20, right: 15, top: 5, bottom: 15 }) } Row() { this.pushLyricButton($r('app.media.cut_current'), 0, '本地歌词') this.pushLyricButton($r('app.media.white_search'), 1, '获取歌词') // this.pushLyricButton($r('app.media.white_search'), 1, '繁体转简体') } .justifyContent(FlexAlign.Center) .margin({ top: 3, bottom: 10 }) } } private setBlurDegree(index: number, isPip: boolean) { if (isPip) { this.blurDegreePip = index this.lyricControllerXF.setBlurDegree(this.blurDegreePip) PreferencesUtil.putSync('setBlurDegreePip', this.blurDegreePip) } else { this.blurDegree = index this.lyricController.setBlurDegree(this.blurDegree) PreferencesUtil.putSync('setBlurDegree', this.blurDegree) } } private setLyricAlignMode(index: number, isPip: boolean) { if (isPip) { this.currentLyricAlignModePip = index if (this.currentLyricAlignModePip === 1) { this.lyricControllerXF.setAlignMode("left") } else { this.lyricControllerXF.setAlignMode("center") } PreferencesUtil.putSync('LyricAlignModePip', this.currentLyricAlignModePip) } else { this.currentLyricAlignMode = index if (this.currentLyricAlignMode === 1) { this.lyricController.setAlignMode("left") } else { this.lyricController.setAlignMode("center") } PreferencesUtil.putSync('LyricAlignMode', this.currentLyricAlignMode) } } private setLyricTextSize(index: number, isPip: boolean) { if (isPip) { this.currentLyricSizePip = index this.lyricControllerXF.setTextSize(this.currentLyricSizePip) PreferencesUtil.putSync('LyricTextSizePip', this.currentLyricSizePip) } else { this.currentLyricSize = index this.lyricController.setTextSize(this.currentLyricSize) PreferencesUtil.putSync('LyricTextSize', this.currentLyricSize) } } private setHighLyricTextSize(scale: number, isPip: boolean) { if (isPip) { this.currentHightLyricSizePip = scale this.lyricControllerXF .setHighlightScale(this.currentHightLyricSizePip) PreferencesUtil.putSync('HighLyricTextSizePip', scale) } else { this.currentHightLyricSize = scale this.lyricController .setHighlightScale(this.currentHightLyricSize) PreferencesUtil.putSync('HighLyricTextSize', scale) } } private setLyricLineSpace(index: number, isPip: boolean) { if (isPip) { this.currentLyricLineSpacePip = index this.lyricControllerXF.setLineSpace(this.currentLyricLineSpacePip) PreferencesUtil.putSync('LyricLineSpacePip', this.currentLyricLineSpacePip) } else { this.currentLyricLineSpace = index this.lyricController.setLineSpace(this.currentLyricLineSpace) PreferencesUtil.putSync('LyricLineSpace', this.currentLyricLineSpace) } } private changeLyricColor(color: string, isPip: boolean, isBG?: boolean) { LogUtil.info('onecold isBG = ' + isBG) if (isBG) { //如果是设置悬浮背景 this.pipBg = color PreferencesUtil.putSync('BGColorPip', this.pipBg) this.nodeController.updateBgColor(this.pipBg, this.lyricControllerXF) return } if (isPip) { this.currentLyricColorPip = color this.lyricControllerXF .setTextColor(this.currentLyricColorPip) PreferencesUtil.putSync('LyricColorPip', this.currentLyricColorPip) } else { this.currentLyricColor = color this.lyricController .setTextColor(this.currentLyricColor) PreferencesUtil.putSync('LyricColor', this.currentLyricColor) } } private changeLyricHightLightColor(color: string, isPip: boolean) { if (isPip) { this.currentHighLightLyricColorPip = color this.lyricControllerXF .setHighlightColor(this.currentHighLightLyricColorPip) PreferencesUtil.putSync('LyricHighLightColorPip', this.currentHighLightLyricColorPip) } else { this.currentHighLightLyricColor = color this.lyricController .setHighlightColor(this.currentHighLightLyricColor) PreferencesUtil.putSync('LyricHighLightColor', this.currentHighLightLyricColor) } } // 构建时间控制器 @Builder BuildTimeControls() { Column() { // 时间显示 Text(this.getTimeText()) .fontSize(18) .fontColor(Color.White) .margin({ bottom: 10 }) Row() { this.PrecisionTimeButton($r('app.media.ic_previous'), -0.5, ' -0.5s ') this.PrecisionTimeButton($r('app.media.loop'), 0, '重置') this.PrecisionTimeButton($r('app.media.ic_next'), 0.5, ' +0.5s ') } .justifyContent(FlexAlign.Center) } .margin({ top: 12, bottom: 8 }) } // 精度按钮组件 @Builder PrecisionTimeButton(icon: Resource, step: number, label: string) { Button({ type: ButtonType.Capsule, stateEffect: true }) { Column() { Image(icon) .width(28) .margin({ bottom: 5 }) .opacity(this.isButtonDisabled(step) ? 0.5 : 1) Text(label) .fontSize(12) .fontColor('#FFFFFF') } } .backgroundColor(Color.Transparent) .enabled(!this.isButtonDisabled(step)) .onClick(() => this.handlePrecisionAdjust(step)) .padding(5) .width(66) .height(66) .margin({ left: 18, right: 18 }) .border({ color: '#FFFFFF', width: 1.8 }) } private handlePrecisionAdjust(step: number) { if (step === 0) { this.timeOffset = 0 return } // 精度处理:保留1位小数 const newOffset = Number((this.timeOffset + step).toFixed(5)) // 边界保护(±15.0秒) if (newOffset < -30.0 || newOffset > 30.0) { // this.triggerEdgeFeedback(newOffset) // 触发边界反馈 return } // 状态更新 this.timeOffset = newOffset } // 按钮禁用逻辑 private isButtonDisabled(step: number): boolean { const futureOffset = this.timeOffset + step return (futureOffset < -30.0) || (futureOffset > 30.0) } // 时间显示文本升级 private getTimeText(): string { const absValue = Math.abs(this.timeOffset) return this.timeOffset === 0 ? '歌词时间已重置' : `歌词已${this.timeOffset < 0 ? '延后' : '提前'} ${absValue.toFixed(1)} 秒` } // 导入 本地歌词 搜索歌词 @Builder pushLyricButton(icon: Resource, step: number, label: string) { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { Image(icon) .width(14) .margin({ left: 13 }) Text(label) .fontSize(12) .margin({ left: 6 }) .fontColor('#FFFFFF') } } .onClick(() => { switch (step) { case 0: this.callFilePickerSelectFileForLyric() break; case 1: //判断有没有设置api if (PreferencesUtil.getStringSync('LRC_API', '') === '') { this.isLyricSetting = false this.showTipsDialog() return } if (StrUtil.isNotEmpty(this.videoUrl)) { ToastUtil.showToast('正在获取') this.isLyricSetting = false let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc' this.initLyric(lyricPath, true); } break; } }) .padding(5) .width(110) .backgroundColor(Color.Transparent) .height(40) .margin({ left: 6, right: 6 }) .border({ color: '#FFFFFF', radius: 20, width: 1.8 }) } showVipDialog() { DialogHelper.showCustomContentDialog({ dialogId: 'vip', title: "友情提示", autoCancel: false, //点击遮障层时,不关闭弹窗 backCancel: true, //点击返回键,不关闭弹窗 contentBuilder: () => { this.customVipBuilder("该功能需开通会员,非常感谢您的支持!") }, buttons: [], }) } //去赞助的自定义内容 @Builder customVipBuilder(content: string) { Column() { Text(content) .fontColor(Color.Gray) .fontSize(16) .alignSelf(ItemAlign.Start) .margin({ bottom: 15 }) .fontSize(16) Row() { Button('取消') .fontColor(Color.White) .backgroundColor(this.themeColor)//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]}) .height(50) .layoutWeight(1) .stateEffect(true) .margin({ right: 6 }) .onClick(() => { DialogHelper.closeDialog('vip'); //关闭弹框 }) Button('开通') .fontColor(Color.White)//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]}) .layoutWeight(1) .height(50) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ left: 6 }) .onClick(() => { DialogHelper.closeDialog('vip'); //关闭弹框 this.isShowPlay = false this.mType = 1 //跳转到用户中心 // router.pushUrl({ // url: 'pages/VipPage' // }, router.RouterMode.Single); }) } } .width("100%") .padding(10) } showTipsDialog() { DialogHelper.showCustomContentDialog({ dialogId: 'tips', title: "友情提示", autoCancel: false, //点击遮障层时,不关闭弹窗 backCancel: true, //点击返回键,不关闭弹窗 contentBuilder: () => { this.customTipsBuilder("请到设置界面配置API服务器地址!") }, buttons: [], }) } @Builder customTipsBuilder(content: string) { Column() { Text(content) .fontColor(Color.Gray) .fontSize(16) .alignSelf(ItemAlign.Start) .margin({ bottom: 15 }) .fontSize(16) Row() { Button('取消') .fontColor(Color.White) .backgroundColor(this.themeColor) .height(50) .layoutWeight(1) .stateEffect(true) .margin({ right: 6 }) .onClick(() => { DialogHelper.closeDialog('tips'); //关闭弹框 }) Button('跳转') .fontColor(Color.White)// .layoutWeight(1) .height(50) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ left: 6 }) .onClick(() => { DialogHelper.closeDialog('tips'); //关闭弹框 this.isShowEdit = false this.longItemFilePath = '' // if(this.isGridMusic&&!this.isShowPlay){ // this.longItemFilePath== '' // }else{ // this.isShowEdit = false // } this.isShowMoreView = false; this.isShowPlay = false this.mType = 4 // router.pushUrl({ // url: 'pages/SettingPage' // }, router.RouterMode.Single); }) } } .width("100%") .padding(10) } @Builder PlayMoreSheet() { Column() { this.MoreList() } .width('100%') .height('100%') } @Builder MoreList() { Scroll() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) { ForEach(this.moreItems, (more: MoreItem, index: number) => { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { if (more.id === 3&&this.currentSong) { Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) .bindSheet($$this.isShowDetailMore, this.detailSheet(this.currentSong), { height: this.isCoverOpacity() ? '95%' : '95%', dragBar: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, showClose: true, blurStyle: BlurStyle.Thin, backgroundColor: Color.Transparent, title: { title: '歌曲信息' } }) } else if (more.id === 15 && this.currentSong) { //编辑标签 Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) .bindSheet($$this.isShowEdit, this.editSheet(this.currentSong), { height: '99%', dragBar: true, showClose: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, // blurStyle:BlurStyle.Thin, // backgroundColor:Color.Transparent, title: { title: '编辑标签' } }) } else if (more.id === 5) { //定时关闭 Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) .onClick(() => { this.doMore(more.id) }) .bindSheet($$this.isShowTimeCloseMore, this.TimeCloseSheet(), { height: this.isCoverOpacity() ? '95%' : '95%', preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, dragBar: true, showClose: true, title: { title: $r('app.string.time_close') } }) } else if (more.id === 2) { //歌词设置 Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) } else if (more.id === 16) { //悬浮歌词设置 Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) .bindSheet($$this.showPipLyric, this.lyricSettingSheet(true), { height: this.isCoverOpacity() ? '95%' : '95%', dragBar: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, showClose: true, blurStyle: BlurStyle.Thin, backgroundColor: Color.Transparent, title: { title: '悬浮歌词' } }) } else if (more.id === 11) { Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) .bindSheet($$this.isJumpSetting, this.JumpTopEndSheet(), { height: this.isCoverOpacity() ? '95%' : '60%', dragBar: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, showClose: true, blurStyle: BlurStyle.Thin, backgroundColor: Color.Transparent, title: { title: '跳过头尾' } }) } else if (more.id === 12) { //投播 AVCastPicker({ customPicker: (): void => this.TPImageBuilder(more), onStateChange: (state) => { if (state == AVCastPickerState.STATE_APPEARING) { console.log(' The picker starts showing.'); } else if (state == AVCastPickerState.STATE_DISAPPEARING) { console.log(' The picker finishes presenting.'); } }, pickerStyle: AVCastPickerStyle.STYLE_PANEL, sessionType: 'video' }) .id('AVCastPicker') } else { Image(more.image) .width(24) .height(24) .margin({ left: 10, right: 10 }) .onClick(() => { this.doMore(more.id) }) Text(more.id == 17 ? (this.isLandscape ? '竖屏模式' : '横屏模式') : more.title) .fontSize(16) .fontColor(Color.White) .onClick(() => { this.doMore(more.id) }) Select([//倍速 { value: '0.25x' }, { value: '0.5x' }, { value: '0.75x' }, { value: '1x' }, { value: '1.25x' }, { value: '1.5x' }, { value: '1.75x' }, { value: '2x' }, { value: '3x' }]) .font({ size: 16, weight: FontWeight.Medium }) .fontColor($r('sys.color.white')) .margin({ left: 25 }) .visibility(more.id === 1 ? Visibility.Visible : Visibility.None) .selected(CommonConstants.video_speed_list.indexOf(this.playSpeed)) .value(Utility.optimizedFormat(this.playSpeed)) .onSelect(async (_index: number, text?: string | undefined) => { let speed = parseFloat(text?.replace('x', '') || '1'); if (!CommonConstants.video_speed_list.includes(speed)) { speed = 1; } this.playSpeed = speed this.mIjkMediaPlayer.setSpeed(this.playSpeed + 'f'); }) Slider({ value: this.volume, min: 0, max: 1, step: 0.1, style: SliderStyle.OutSet }) .margin({ left: 18, right: 8 }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(9) .visibility(more.id === 9 ? Visibility.Visible : Visibility.None) .onChange((value: number) => { this.volume = value; this.mIjkMediaPlayer.setVolume(this.volume.toString(), this.volume.toString()); PreferencesUtil.putSync('DefalutVolume',this.volume) }) .layoutWeight(1) Blank() Toggle({ type: ToggleType.Switch, isOn: this.isMusicMemoryPlay }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .margin({ right: 15 }) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .visibility(more.id == 6 ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => { // 选择开关状态变化时触发事件 if (more.id == 6) { this.isMusicMemoryPlay = checked; PreferencesUtil.put(SettingPage.iS_MUSIC_MEMORY_PLAY, this.isMusicMemoryPlay) if (this.isMusicMemoryPlay) { ToastUtil.showToast('记忆播放已开启') } else { ToastUtil.showToast('记忆播放已关闭') } } }) .width(48) .height(24); Toggle({ type: ToggleType.Switch, isOn: this.isMusicBGCover }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ right: 15 }) .visibility(more.id == 7 ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => { // 选择开关状态变化时触发事件 if (more.id == 7) { this.isMusicBGCover = checked; PreferencesUtil.put(SettingPage.iS_MUSIC_BG_COVER, this.isMusicBGCover) if (this.isMusicBGCover) { ToastUtil.showToast('播放背景随音乐封面已开启') } else { ToastUtil.showToast('播放背景随音乐封面已关闭') } } }) .width(48) .height(24); Toggle({ type: ToggleType.Switch, isOn: this.isSavePlayMode }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ right: 15 }) .visibility(more.id == 8 ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => { // 选择开关状态变化时触发事件 if (more.id == 8) { this.isSavePlayMode = checked; PreferencesUtil.put(SettingPage.iS_SAVE_PLAY_MODE, this.isSavePlayMode) if (this.isSavePlayMode) { ToastUtil.showToast('保存播放模式已开启') } else { ToastUtil.showToast('保存播放模式已关闭') } } }) .width(48) .height(24); Toggle({ type: ToggleType.Switch, isOn: this.isCoverRectangle }) .selectedColor(this.themeColor) .switchPointColor(Color.White) .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 }) .margin({ right: 15 }) .visibility(more.id == 13 ? Visibility.Visible : Visibility.None) .onChange((checked: boolean) => { // 选择开关状态变化时触发事件 if (more.id == 13) { this.isCoverRectangle = checked; PreferencesUtil.put(SettingPage.IS_COVER_RECTANGLE, this.isCoverRectangle) if (this.isCoverRectangle) { ToastUtil.showToast('封面方形已开启') this.animationRoFun() } else { ToastUtil.showToast('封面圆形已开启') this.animationRoFun() } } }) .width(48) .height(24); } } .width('100%') .height(45) .padding(15) .backgroundColor(Color.Transparent) .borderRadius(12) .margin({ bottom: 6 }) } .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.9 }) .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index })) .backgroundColor(Color.Transparent) .onClick(() => { this.doMore(more.id) }) }); } .width('100%') .padding(16) }.margin({ bottom: 18 }) } @Builder TPImageBuilder(more: MoreItem): void { Row() { Image($r('app.media.tuoping')) .width(22) .height(22) .margin({ left: 10, right: 10 }) Text(more.title) .fontSize(16) .fontColor(Color.White) } .width('100%') .layoutWeight(1) .alignItems(VerticalAlign.Center) .justifyContent(FlexAlign.Start) } private moreItems: MoreItem[] = [ { id: 1, image: $r('app.media.speed'), title: '倍速' }, { id: 2, image: $r('app.media.lyric'), title: '歌词' }, { id: 17, image: $r('app.media.full_4'), title: '横屏模式' }, { id: 16, image: $r('app.media.hua_zhong_hua'), title: '悬浮歌词' }, { id: 14, image: $r('app.media.cover_online'), title: '获取封面' }, { id: 15, image: $r('app.media.rename2'), title: '编辑标签' }, { id: 5, image: $r('app.media.time_close'), title: '定时关闭' }, { id: 10, image: $r('app.media.ring_tone'), title: '设为铃声' }, { id: 11, image: $r('app.media.time_close'), title: '跳过头尾' }, { id:20,image: $r("app.media.a_b"), title: 'AB循环' }, { id: 12, image: $r('app.media.toubo'), title: '投播' }, { id: 3, image: $r('app.media.hua_zhong_hua'), title: '详情' }, { id: 4, image: $r('app.media.share2'), title: '分享' }, { id: 9, image: $r('app.media.volume_white'), title: '调整音量' }, // { id: 6, image: $r('app.media.memory'), title: '记忆播放' }, { id: 13, image: $r('app.media.rectangle'), title: '封面方形' }, { id: 8, image: $r('app.media.cut_current'), title: '保存播放模式' }, { id: 7, image: $r('app.media.playlist2'), title: '播放背景随音乐封面' }, ]; @State isShowMoreView: boolean = false; @State isMusicMemoryPlay: boolean = false //是否启用记忆播放 @State isShowTimeCloseMore: boolean = false @State isShowDetailMore: boolean = false @State isMusicBGCover: boolean = true @State isSavePlayMode: boolean = true @State isLyricSetting: boolean = false @State isJumpSetting: boolean = false @State isShowEdit: boolean = false @State showPipLyric: boolean = false async setRingTone() { if (StrUtil.isEmpty(this.videoUrl) || StrUtil.isEmpty(this.name)) { return } // 确定后的逻辑 let ringtoneTypeList: Array = ringtone.getSupportedRingtoneTypes(); LogUtil.info('onecold getSupportedRingtoneTypes : ' + JSON.stringify(ringtoneTypeList)); let dataTypeList: Array = ringtone.getSupportedDataTypes(ringtone.RingtoneType.NOTIFICATION); LogUtil.info('onecold getSupportedDataTypes: ' + JSON.stringify(dataTypeList)); //let fileName: string = audioPath.substring(audioPath.lastIndexOf('/') + 1, audioPath.lastIndexOf('.')); await ringtone.startRingtoneSetting(getContext(this) as common.UIAbilityContext , this.videoUrl, this.name).then(res => { LogUtil.info('onecold setFlag :' + res); }); } doMore(moreId: number) { switch (moreId) { case 2: //歌词 this.isLyricSetting = !this.isLyricSetting; this.isShowMoreView = false break; case 3: //详情 this.isShowDetailMore = !this.isShowDetailMore; break; case 4: //分享 if (this.currentSong !== undefined) { Utility.doShareMusic(this.currentSong, getContext(this) as common.UIAbilityContext) } this.isShowMoreView = false break; case 5: //定时关闭 this.isShowTimeCloseMore = !this.isShowTimeCloseMore; break; case 10: //设为铃声 this.setRingTone() this.isShowMoreView = false break; case 11: //跳过头尾 this.isJumpSetting = !this.isJumpSetting; break; case 13: //倍速 break; case 14: //搜索封面 if (this.currentSong !== undefined) { this.doSearchCover(this.currentSong) } this.isShowMoreView = false break; case 15: //编辑标签 this.setEditStrEmpty() this.tempLyricContent = this.lyricContent this.isShowEdit = !this.isShowEdit; break; case 16: //悬浮歌词 this.showPipLyric = !this.showPipLyric; break; case 17: //横屏模式 if (this.isLandscape) { this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT); } else { this.setOrientation(window.Orientation.USER_ROTATION_LANDSCAPE); } this.isShowMoreView = false break; case 20://AB循环 this.isOpenAB = !this.isOpenAB; this.isABSheet = false break; } } //AB循环 @State isOpenAB:boolean = false @State jumpATime:number = 0 @State jumpBTime:number = 0 @State isABSheet:boolean = false @Builder ABLoopSheet() { ABLoopComptent({ jumpATime:this.jumpATime, jumpBTime:this.jumpBTime, onClickAItem: async ()=>{ if(this.mIjkMediaPlayer){ this.jumpATime = await this.mIjkMediaPlayer.getCurrentPosition() } }, onClickBItem: async ()=>{ if(this.mIjkMediaPlayer){ this.jumpBTime = await this.mIjkMediaPlayer.getCurrentPosition() if(this.jumpBTime<=this.jumpATime){ ToastUtil.showToast('设置B点时间不能小于A点时间') this.jumpBTime = 0 } } }, onCancleButtonClick: async ()=>{ this.isOpenAB = false this.isABSheet = false }, onStartButtonClick: async ()=>{ this.isABSheet = false this.startABLoop() }, }) } //开始AB循环 startABLoop(){ if(this.mIjkMediaPlayer){ this.seekTo(this.jumpATime+'') this.isOpenAB = true } } /** * 切换横竖屏 * */ setOrientation(orientation: number) { this.windowClass.setPreferredOrientation(orientation).then(() => { Logger.info('setWindowOrientation: ' + orientation + ' Succeeded.'); }).catch((err: BusinessError) => { Logger.info('setWindowOrientation: ' + orientation + ' Failed. Cause: ' + JSON.stringify(err)); }); } /** * 画中画功能(悬浮歌词功能) * */ private nodeController: TextNodeController = new TextNodeController('#000000', this.lyricControllerXF); pipController?: PiPWindow.PiPController; navigationId: string = ''; @State curState: string = ''; @State curError: ResourceStr = ''; @State pipBg: string = '#E8E8E8' @State buttonAction: string = ''; //开启画中画功能 async startPip() { this.nodeController = new TextNodeController(this.pipBg, this.lyricControllerXF); if (!this.pipController) { await this.createPipController(); } if (!this.pipController) { Logger.info(`[${TAG}] pipController create error`); return; } await this.pipController.startPiP(); } async stopPip() { if (!this.pipController) { Logger.info(`[${TAG}] pipController is not exist`); return; } this.isOpenPip = false await this.pipController.stopPiP(); } async createPipController() { this.pipController = await PiPWindow.create({ context: getContext(this), componentController: this.xcomponentController, navigationId: this.navigationId, templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY, controlGroups: [PiPWindow.VideoPlayControlGroup.VIDEO_PREVIOUS_NEXT], customUIController: this.nodeController, }); this.pipLyWidth = PreferencesUtil.getNumberSync('pipLyWidth', 330) this.pipLyHeight = PreferencesUtil.getNumberSync('pipLyHeight', 120) this.pipController.updateContentSize(this.pipLyWidth, this.pipLyHeight); this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => { this.onStateChange(state, reason); }); this.pipController.on('controlPanelActionEvent', (event: PiPWindow.PiPActionEventType, status?: number) => { this.onActionEvent(event, status); }); } destroyPipController() { if (!this.pipController) { return; } this.pipController.off('stateChange'); this.pipController.off('controlPanelActionEvent'); this.pipController = undefined; } onStateChange(state: PiPWindow.PiPState, reason: string) { switch (state) { case PiPWindow.PiPState.ABOUT_TO_START: this.curState = 'ABOUT_TO_START'; this.curError = $r('app.string.current_error_hint'); break; case PiPWindow.PiPState.STARTED: this.curState = 'STARTED'; this.curError = $r('app.string.current_error_hint'); break; case PiPWindow.PiPState.ABOUT_TO_STOP: this.curState = 'ABOUT_TO_STOP'; this.curError = $r('app.string.current_error_hint'); break; case PiPWindow.PiPState.STOPPED: this.curState = 'STOPPED'; this.curError = $r('app.string.current_error_hint'); this.stopPip() this.destroyPipController() break; case PiPWindow.PiPState.ABOUT_TO_RESTORE: this.curState = 'ABOUT_TO_RESTORE'; this.curError = $r('app.string.current_error_hint'); break; case PiPWindow.PiPState.ERROR: this.curState = 'ERROR'; this.curError = reason; break; default: break; } Logger.info(`[${TAG}] onecold onStateChange: ${this.curState}, reason: ${reason}`); } onActionEvent(event: PiPWindow.PiPActionEventType, status: number | undefined) { LogUtil.info('onecold onActionEvent = ' + event + ' status=' + status) switch (event) { case 'playbackStateChanged': if (status === 0) { this.pause(); } else { console.info('onecold startPlayOrResumePlay 9869') this.startPlayOrResumePlay(); } break; case 'nextVideo': // 切换到下一个视频 this.playNext() break; case 'previousVideo': // 切换到上一个视频 this.playPrevious() break; case 'previousVideo': // 切换到上一个视频 this.playPrevious() break; default: } this.buttonAction = event + `-status:${status}`; Logger.info(`[${TAG}] onActionEvent: ${this.buttonAction} status:${status}}`); } /** * 画中画功能(悬浮歌词功能)结束 * */ async doSearchCover(item: VideoItem): Promise { if (PreferencesUtil.getStringSync('COVER_API', '') === '') { this.isShowMoreView = false; this.showTipsDialog(); return ''; // Return empty string when no API is configured } if (!item) { return ''; // Return empty string if item is undefined } const artist = item?.artist || ''; // Use empty string if artist is missing return this.searchCover(item, item.name, artist); // Await is not needed here as we return the promise directly } /** *api搜索封面 *item,根据title和artist */ searchCover(item: VideoItem, title: string, artist: string): Promise { return NetAxiosUtil.getLyricCover(title, artist, PreferencesUtil.getStringSync('COVER_API', '')).then(async (res) => { LogUtil.debug("onecold res =" + res); if (StrUtil.isNotEmpty(res) && res !== 'unknown' && res !== 'Timeout was reached') { if (item.filePath == this.currentSong?.filePath) { this.cover = res; if (this.currentSong) { this.currentSong.pixelMapPath = res; } } // Update pixel map path but always return res regardless of success this.table.updatePixelMapPath(item.filePath, res, (success: boolean, error?: string) => { if (success) { this.doUpdateData(); console.log("onecold 更新音乐封面成功,数据库已同步"); } else { console.error("onecold 更新音乐封面数据库失败原因: " + error); } // Note: We don't resolve/reject here because we already returned res }); LogUtil.debug("onecold this.cover =" + this.cover); } return res; // Always return res regardless of updatePixelMapPath result }); } @State jumpTopTime: number = 0 @State jumpEndTime: number = 0 @State isOpenJump: boolean = false @Builder JumpTopEndSheet() { Scroll() { Column() { Column() { Text('仅对本播放列表有效') .fontSize(16) .fontColor($r('app.color.text_color')) // 跳过头尾 Column() { Row() { Text('跳过片头:') .fontSize(16) .fontColor($r('app.color.text_color')) Text(this.jumpTopTime + 's') .fontSize(16) .fontColor($r('app.color.text_color')) } .margin({ bottom: 5 }) Row() { Slider({ value: this.jumpTopTime, min: 0, max: 120, step: 1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(8) .onChange((value: number) => { this.jumpTopTime = value; }) .width('85%') } .alignItems(VerticalAlign.Top) } .margin({ top: 10, bottom: 10 }) // 跳过头尾 Column() { Row() { Text('跳过片尾:') .fontSize(16) .fontColor($r('app.color.text_color')) Text(this.jumpEndTime + 's') .fontSize(16) .fontColor($r('app.color.text_color')) } .margin({ bottom: 5 }) Row() { Slider({ value: this.jumpEndTime, min: 0, max: 120, step: 1, style: SliderStyle.OutSet }) .blockColor(this.themeColor) .trackColor($r('app.color.speed_text_color')) .selectedColor(Color.White) .trackThickness(8) .onChange((value: number) => { this.jumpEndTime = value; }) .width('85%') } .alignItems(VerticalAlign.Top) } .margin({ top: 10, bottom: 10 }) Row() { Button('保存设置') .fontColor(Color.White) .layoutWeight(1) .height(40) .width(80) .backgroundColor(this.themeColor) .stateEffect(true) .margin({ left: 0, bottom: 20 }) .onClick(async () => { if (!Utility.isNoble()) { this.isJumpSetting = false this.isShowMoreView = false this.showVipDialog() return } if (this.currentSong === undefined) { return } if (this.modeType === 0) { let parentPath = FileUtil.getParentPath(this.videoUrl) PreferencesUtil.putSync(parentPath + 'isOpenJump', true) PreferencesUtil.putSync(parentPath + 'jumpEndTime', this.jumpEndTime) PreferencesUtil.putSync(parentPath + 'jumpTopTime', this.jumpTopTime) } else if (this.modeType === 1) { //媒体库 PreferencesUtil.putSync(1 + 'isOpenJump', true) PreferencesUtil.putSync(1 + 'jumpEndTime', this.jumpEndTime) PreferencesUtil.putSync(1 + 'jumpTopTime', this.jumpTopTime) } else if (this.modeType === 2) { //艺术家 if (this.currentSong?.artist) { PreferencesUtil.putSync(this.currentSong?.artist + 2 + 'isOpenJump', true) PreferencesUtil.putSync(this.currentSong?.artist + 2 + 'jumpEndTime', this.jumpEndTime) PreferencesUtil.putSync(this.currentSong?.artist + 2 + 'jumpTopTime', this.jumpTopTime) } } else if (this.modeType === 3) { //专辑 if (this.currentSong?.album) { PreferencesUtil.putSync(this.currentSong?.album + 3 + 'isOpenJump', true) PreferencesUtil.putSync(this.currentSong?.album + 3 + 'jumpEndTime', this.jumpEndTime) PreferencesUtil.putSync(this.currentSong?.album + 3 + 'jumpTopTime', this.jumpTopTime) } } ToastUtil.showShort("保存成功") this.isJumpSetting = false }) } .margin({ left: 38, right: 38, top: 10, bottom: 10 }) .width(120) .height(50) } } } .width('100%') .height('100%') } // 应用可以通过onStateChange接口监听组件显示/消失状态,当组件显示时建议不要销毁AVCastPicker的显示;当组件消失时,再根据业务隐藏AVCastPicker。 private onStateVastChange(state: AVCastPickerState) { if (state == AVCastPickerState.STATE_APPEARING) { console.log('The picker starts showing.'); } else if (state == AVCastPickerState.STATE_DISAPPEARING) { console.log('The picker finishes presenting.'); } } private showSelectLyric() { DialogHelper.showActionSheetDialog({ title: '温馨提醒:\n1.歌词文件名必须和目标歌曲文件名相同\n2.歌词文件和目标歌曲要同个歌单路径。', // sheets: ["本地歌词", "搜索歌词"], sheets: [ { value: "本地歌词", fontColor: $r('app.color.text_color') }, { value: "搜索歌词", fontColor: $r('app.color.text_color') }, ], maskColor: Color.Transparent, backgroundColor: $r('app.color.bg_card'), transition: AnimationHelper.transitionInDown(666), onAction: (index) => { switch (index) { case 0: this.callFilePickerSelectFileForLyric() break; case 1: if (StrUtil.isNotEmpty(this.videoUrl)) { let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc' this.initLyric(lyricPath); } break; } } }) } // 拉起picker选择文件管理器 async callFilePickerSelectFileForLyric(): Promise { try { let DocumentSelectOptions = new picker.DocumentSelectOptions(); DocumentSelectOptions.maxSelectNumber = 1 DocumentSelectOptions.fileSuffixFilters = CommonConstants.LYRIC_FORMAT let documentPicker = new picker.DocumentViewPicker(); documentPicker.select(DocumentSelectOptions).then((DocumentSelectResult) => { Logger.info(TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + JSON.stringify(DocumentSelectResult)); if (DocumentSelectResult !== null && DocumentSelectResult !== undefined) { this.saveVideoDatasLyric(DocumentSelectResult) } }).catch((err: BusinessError) => { Logger.error(TAG, 'DocumentViewPicker.select failed with err: ' + JSON.stringify(err)); }); } catch (err) { Logger.error(TAG, 'DocumentViewPicker failed with err: ' + JSON.stringify(err)); } } async saveVideoDatasLyric(uris: string[]) { if (ArrayUtil.isEmpty(uris)) { return } let newUris: string[] = []; for (let i = 0; i < uris.length; i++) { let filePath = getContext(this).filesDir + '/' + Utility.getMediaNameByUri(uris[i]) Logger.info(TAG, 'filePath uri: ' + filePath); newUris.push(filePath) let file = fileIo.openSync(uris[i], fileIo.OpenMode.READ_ONLY) let file2 = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE) fileIo.copyFileSync(file.fd, file2.fd) fileIo.closeSync(file); fileIo.closeSync(file2); this.initLyric(filePath,false,false,true) } } private initDelayPlay(context: object) { this.mContext = context; } startPipLyric() { if (this.pipController) { this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE, PiPWindow.PiPControlStatus.PLAY); } this.isOpenPip = PreferencesUtil.getBooleanSync('isOpenPip', false) if (this.isOpenPip) { this.startPip() } else { this.stopPip() } } //startOffset 从cue分轨时间开始播 private startPlayOrResumePlay(startOffset?:number) { //针对dsf文件高采样率sampleRate>441000,统一转wav播放 const sampleRate = this.currentSong?.sampleRate||0 if(this.videoUrl.toLowerCase().endsWith('.dsf')&&sampleRate>=441000){ console.info('onecold 采样率 ='+sampleRate) let newDsfPath = this.context.filesDir + '/'+FileUtil.getFileName(this.videoUrl)+'.wav' console.info('onecold newDsfPath ='+newDsfPath) if(!FileUtil.accessSync(newDsfPath)){ const task = new taskpool.Task(convertDsfToWav,this.videoUrl,newDsfPath); taskpool.execute(task, taskpool.Priority.HIGH).then((data)=>{ this.videoUrl = newDsfPath this.startPlay(startOffset) }).catch((e:object)=>{ console.info("task1 catch e: " + e); }) }else{ this.videoUrl = newDsfPath this.startPlay(startOffset) } }else{ this.startPlay(startOffset) } } startPlay(startOffset?:number) { this.startPipLyric() this.mDestroyPage = false; this.animationState = AnimationStatus.Running LogUtils.getInstance().LOGI("onecold startPlayOrResumePlay start this.CONTROL_PlayStatus:" + this.CONTROL_PlayStatus) if (this.CONTROL_PlayStatus == PlayStatus.INIT) { this.stopProgressTask(); this.startProgressTask(); this.play(this.videoUrl.toString(),startOffset); } if (this.CONTROL_PlayStatus == PlayStatus.PAUSE) { this.mIjkMediaPlayer.start(); this.setProgress() this.CONTROL_PlayStatus = PlayStatus.PLAY } this.setIsPlaying(true) this.updateSessionPlayState(true) this.watchStatus(); this.updateLastPlayTimeStr(this.videoUrl) } updateLastPlayTimeStr(filePath: string) { let lastPlayTime = DateUtil.getTodayStr('yyyy-MM-dd HH:mm:ss') this.table.updateLastPlayedStrByFilePath(filePath, lastPlayTime, (success: boolean, error?: string) => { if (success) { this.getHistoryList(false) console.log(" onecold 更新最近播放时间成功,数据库已同步"); } else { console.error(" onecold 更新最近播放时间数据库失败原因: " + error); } }); } private completionNum(num: number): string | number { if (num < 10) { return '0' + num; } else { return num; } } private stringForTime(timeMs: number): string { let totalSeconds: number | string = (timeMs / 1000); let newSeconds: number | string = totalSeconds % 60; let minutes: number | string = (totalSeconds / 60) % 60; let hours: number | string = totalSeconds / 3600; //LogUtils.getInstance().LOGI("stringForTime hours:" + hours + ",minutes:" + minutes + ",seconds:" + newSeconds); hours = this.completionNum(Math.floor(Math.floor(hours * 100) / 100)); minutes = this.completionNum(Math.floor(Math.floor(minutes * 100) / 100)); newSeconds = Math.floor(Math.floor(newSeconds * 100) / 100) if (this.isCurrentTime) { if (this.oldSeconds < newSeconds || newSeconds === 0 || this.isSeekTo) { this.oldSeconds = newSeconds } else { newSeconds = this.oldSeconds } } newSeconds = this.completionNum(newSeconds); if (hours > 0) { return hours + ":" + minutes + ":" + newSeconds; } else { return minutes + ":" + newSeconds; } } private setProgress() { let position = this.mIjkMediaPlayer.getCurrentPosition(); let duration = this.mIjkMediaPlayer.getDuration(); let pos = 0; if (duration > 0) { this.slideEnable = true; let curPercent = position / duration; pos = curPercent * 100; if (pos > this.PROGRESS_MAX_VALUE) { this.progressValue = this.PROGRESS_MAX_VALUE } else { this.progressValue = pos; } } // LogUtils.getInstance() // .LOGI("setProgress position:" + position + ",duration:" + duration + ",progressValue:" + pos); this.totalTime = this.stringForTime(duration); if (position > duration) { position = duration; } this.isCurrentTime = true; this.lyricController.updatePosition(position + this.timeOffset * 1000) this.lyricControllerXF.updatePosition(position + this.timeOffset * 1000) this.lyricControllerSingle.updatePosition(position + this.timeOffset * 1000) this.currentTime = this.stringForTime(position); this.isCurrentTime = false if (this.mIjkMediaPlayer.isPlaying()) { this.randomColor = `rgb(${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)})` //如果是开启AB循环 if(this.isOpenAB){ if(position >=this.jumpBTime){ this.seekTo(this.jumpATime+'') return } } // 判断是否播放到结束时间 if (this.isOpenJump && duration > this.jumpEndTime * 1000) { if (position >= duration - this.jumpEndTime * 1000) { if(this.playType == 1){//跳过尾部如果是单曲循环就选择单曲循环 this.CONTROL_PlayStatus = PlayStatus.INIT this.startPlayOrResumePlay(); }else if (this.playType == 2) { //2:正常播放,单曲播完 this.showRePlay(); this.currentTime = this.stringForTime(this.mIjkMediaPlayer.getDuration()); this.progressValue = this.PROGRESS_MAX_VALUE; this.slideEnable = false; this.stop(); // ToastUtil.showToast('开始重新播放') }else{ this.playNext() } } } } else { this.randomColor = 'rbg(0,0,0)' } } private startProgressTask() { let that = this; this.updateProgressTimer = setInterval(() => { //LogUtils.getInstance().LOGI("startProgressTask"); if (!that.mDestroyPage) { that.setProgress(); } }, 300); } private stopProgressTask() { LogUtils.getInstance().LOGI("stopProgressTask"); clearInterval(this.updateProgressTimer); } private showLoadIng() { this.loadingVisible = Visibility.Visible; this.replayVisible = Visibility.None; } private hideLoadIng() { this.loadingVisible = Visibility.None; this.replayVisible = Visibility.None; } private showRePlay() { this.loadingVisible = Visibility.None; this.replayVisible = Visibility.Visible; } private async play(url: string,startOffset?:number) { let that = this; that.showLoadIng(); this.isOpenAB = false //设置XComponent回调的context if (!!this.mContext) { this.mIjkMediaPlayer.setContext(this.mContext, 'xcomponentId'); } if (this.CONTROL_PlayStatus == PlayStatus.INIT) { this.mIjkMediaPlayer.reset(); } this.CONTROL_PlayStatus = PlayStatus.PLAY; //设置debug模式 this.mIjkMediaPlayer.setDebug(false); //初始化配置 this.mIjkMediaPlayer.native_setup(); // 初始化配置后需要重新设置音频流音量,否则音量为默认值1.0 针对dsf格式设置最大值1.0 if(this.volumeSmall){ if(this.videoUrl.toLowerCase().endsWith('.dsf')){ this.mIjkMediaPlayer.setVolume('1', '1'); }else{ this.mIjkMediaPlayer.setVolume(this.volume.toString(), this.volume.toString()); } }else{ this.mIjkMediaPlayer.setVolume(this.volume.toString(), this.volume.toString()); } //设置视频源 this.mIjkMediaPlayer.setDataSource(url); //设置视频源http请求头 let headers = new Map([ ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"], ["referer", "https://www.bilibili.com"] ]); this.mIjkMediaPlayer.setDataSourceHeader(headers); // if(PreferencesUtil.getBooleanSync(SettingPage.IS_MIDIACODEC_OPEN,false)){ // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", "1"); // 启用硬件解码,会导致无法顺序播放 // } this.isMusicBGCover = PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_BG_COVER, true) //使用精确寻帧 例如,拖动播放后,会寻找最近的关键帧进行播放,很有可能关键帧的位置不是拖动后的位置,而是较前的位置.可以设置这个参数来解决问题 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", "1"); const fileSize = await this.getFileSize(this.videoUrl); let bufferSize = 1 * 1024 ; // 默认缓冲区大小为1MB if (fileSize > 3 * 1024 * 1024 * 1024) { // 文件大于3GB bufferSize = 25 * 1024 * 1024; // 设置缓冲区大小为25MB } // 使用软件解码(通常比硬件解码更节省内存) // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", '0'); // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", '0'); // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", '1024'); // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", '500000'); //预读数据的缓冲区大小.修复大文件播放前几秒挂掉的bug,太大了会导致倍数播放失效 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", bufferSize + ''); //停止预读的最小帧数 // this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100"); this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", '1'); ////重连模式,如果中途服务器断开了连接,让它重新连接 // 设置其他缓冲相关选项 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, "start-on-prepared", "1"); // 设置无缓冲,这是播放器的缓冲区,有数据就播放 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0"); //跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5"); // 最大缓冲cache是3s, 有时候网络波动,会突然在短时间内收到好几秒的数据 // 因此需要播放器丢包,才不会累积延时 // 这个和第三个参数packet-buffering无关。 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000"); // 无限制收流 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1"); this.mIjkMediaPlayer.setOptionLong(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1") // 屏幕常亮 this.mIjkMediaPlayer.setScreenOnWhilePlaying(true); // 设置超时 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "10000000"); this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "10000000"); this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000"); this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000"); // 变速播放 this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "1"); this.mIjkMediaPlayer.setSpeed(this.playSpeed + 'f'); let Speed = this.mIjkMediaPlayer.getSpeed() LogUtils.getInstance().LOGI('getSpeed--' + Speed) //是否开启循环播放 // this.mIjkMediaPlayer.setLoopCount(this.loop); let mOnVideoSizeChangedListener: OnVideoSizeChangedListener = { onVideoSizeChanged: (width: number, height: number, sar_num: number, sar_den: number) => { if (height === 0) { return; } const va = width / height; const vpa = that.videoParentAspectRatio; if (vpa > va) { that.videoWidth = (width / (height * vpa)) * 100 + '%'; } else { that.videoWidth = '100%'; } if (width && height) { that.videoAspectRatio = width / height; } LogUtils.getInstance() .LOGI("setOnVideoSizeChangedListener-->go:" + width + "," + height + "," + sar_num + "," + sar_den); that.hideLoadIng(); } } this.mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); let mOnPreparedListener: OnPreparedListener = { onPrepared: async () => { LogUtils.getInstance().LOGI("setOnPreparedListener-->go"); try { // this.cover = this.songList[this.curIndex].pixelMapPath // this.artist = this.songList[this.curIndex].artist // this.videoUrl = this.songList[this.curIndex].filePath let isBgCover = PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_BG_COVER, true) if (isBgCover && this.cover !== undefined && StrUtil.isNotEmpty(this.cover)) { this.setImageColor2(this.cover) } else { this.getImageColor(); } //保存碰一碰分享的当前的歌曲 AppStorage.setOrCreate('currentSong',this.currentSong) ; let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc' await this.initLyric(lyricPath); setTimeout(() => { this.avSessionController.initAvSession(false); this.avSessionController.setAVMetadataMusic(this.songList[this.curIndex], this.duration, this.lyricContent); this.setAvSessionListener(); this.updateSessionPlayState(true) this.setCurrentPlayMode() }, 500) if(startOffset&&startOffset>0){//cue分轨播放 this.seekTo(startOffset + ""); this.saveLastPlayList() return } //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。 let timeoutPlay = 0 if (StrUtil.isNotEmpty(this.videoUrl) && (this.videoUrl.toLowerCase().endsWith('.rmvb') || this.videoUrl.toLowerCase().endsWith('.flac'))) { timeoutPlay = 1000 } //跳过片头片尾关键代码,以下是跳过片头的关键代码 if (this.modeType === 0) { //文件夹 let parentPath = FileUtil.getParentPath(this.videoUrl) this.jumpTopTime = PreferencesUtil.getNumberSync(parentPath + 'jumpTopTime') this.jumpEndTime = PreferencesUtil.getNumberSync(parentPath + 'jumpEndTime') this.isOpenJump = PreferencesUtil.getBooleanSync(parentPath + 'isOpenJump', false) } else if (this.modeType === 1) { //媒体库 this.jumpTopTime = PreferencesUtil.getNumberSync(1 + 'jumpTopTime') this.jumpEndTime = PreferencesUtil.getNumberSync(1 + 'jumpEndTime') this.isOpenJump = PreferencesUtil.getBooleanSync(1 + 'isOpenJump', false) } else if (this.modeType === 2) { //艺术家 if (this.currentSong?.artist) { this.jumpTopTime = PreferencesUtil.getNumberSync(this.currentSong?.artist + 2 + 'jumpTopTime') this.jumpEndTime = PreferencesUtil.getNumberSync(this.currentSong?.artist + 2 + 'jumpEndTime') this.isOpenJump = PreferencesUtil.getBooleanSync(this.currentSong?.artist + 2 + 'isOpenJump', false) } } else if (this.modeType === 3) { //专辑 if (this.currentSong?.album) { this.jumpTopTime = PreferencesUtil.getNumberSync(this.currentSong?.album + 3 + 'jumpTopTime') this.jumpEndTime = PreferencesUtil.getNumberSync(this.currentSong?.album + 3 + 'jumpEndTime') this.isOpenJump = PreferencesUtil.getBooleanSync(this.currentSong?.album + 3 + 'isOpenJump', false) } } this.duration = this.mIjkMediaPlayer.getDuration() if (this.isOpenJump) { //用户是否设置跳过片头片尾 if (this.mIjkMediaPlayer != null) { let jumpTopTimeMs = this.jumpTopTime*1000; if (jumpTopTimeMs <= this.duration) { // 调用 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在读取记忆播放。 if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) { this.seekTo(this.jumpTopTime * 1000 + ""); // 跳到开头 } }, timeoutPlay) } else { // 提示用户无法跳过 ToastUtil.showToast('跳过时间超过视频时长'); } } } else { this.jumpTopTime = 0 this.jumpEndTime = 0 console.error('onecold 最后一首: isFirstStartPlay = ' + this.isFirstStartPlay); console.error('onecold 最后一首: isMemoryLastPlay = ' + this.isMemoryLastPlay); console.error('onecold 最后一首: PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false) = ' + PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false)); //记忆播放(每一首都会记忆播放) if (StrUtil.isNotEmpty(this.videoUrl) && !this.videoUrl.toLowerCase().endsWith('.ts')) { setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。 if (PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_MEMORY_PLAY, false)) { this.restorePlaybackPosition() //读取播放记忆功能 } else if (this.isMemoryLastPlay && this.isFirstStartPlay) { console.error('onecold 最后一首:读取播放记忆功能 = ' + this.isFirstStartPlay); this.restorePlaybackPosition() //读取播放记忆功能 } this.isFirstStartPlay = false }, timeoutPlay) } else { this.isFirstStartPlay = false } } this.saveLastPlayList() } catch (error) { let err: BusinessError = error as BusinessError; console.error('onecold failed with err: ' + JSON.stringify(err)); } } } this.mIjkMediaPlayer.setOnPreparedListener(mOnPreparedListener); let mOnTimedTextListener: OnTimedTextListener = { onTimedText: () => { } } this.mIjkMediaPlayer.setOnTimedTextListener(mOnTimedTextListener) let mOnCompletionListener: OnCompletionListener = { onCompletion: () => { LogUtils.getInstance().LOGI("OnCompletionListener-->go") if (this.playType == 0) { //0:连续播放 1:单片重复播放 2:正常播放 this.playNext() } else if (this.playType == 1) { //1:单片重复播放 this.CONTROL_PlayStatus = PlayStatus.INIT console.info('onecold startPlayOrResumePlay 10592') that.startPlayOrResumePlay(); } else if (this.playType == 2) { //2:正常播放,单曲播完 that.showRePlay(); that.currentTime = that.stringForTime(this.mIjkMediaPlayer.getDuration()); that.progressValue = this.PROGRESS_MAX_VALUE; that.slideEnable = false; that.stop(); // ToastUtil.showToast('开始重新播放') } else if (this.playType == 3) { //3:随机播放 this.randomPlay() } } } this.mIjkMediaPlayer.setOnCompletionListener(mOnCompletionListener); let mOnBufferingUpdateListener: OnBufferingUpdateListener = { onBufferingUpdate: (percent: number) => { LogUtils.getInstance().LOGI("OnBufferingUpdateListener-->go:" + percent); let MediaInfo = this.mIjkMediaPlayer.getMediaInfo() LogUtils.getInstance().LOGI('getMediaInfo---' + MediaInfo); let VideoWidth = this.mIjkMediaPlayer.getVideoWidth() LogUtils.getInstance().LOGI('getVideoWidth---' + VideoWidth); let VideoHeight = this.mIjkMediaPlayer.getVideoHeight() LogUtils.getInstance().LOGI('getVideoHeight---' + VideoHeight); let VideoSarNum = this.mIjkMediaPlayer.getVideoSarNum() LogUtils.getInstance().LOGI('getVideoSarNum---' + VideoSarNum); let VideoSarDen = this.mIjkMediaPlayer.getVideoSarDen() LogUtils.getInstance().LOGI('getVideoSarDen---' + VideoSarDen); let AudioSessionId = this.mIjkMediaPlayer.getAudioSessionId() LogUtils.getInstance().LOGI('getAudioSessionId---' + AudioSessionId); let Looping = this.mIjkMediaPlayer.isLooping() LogUtils.getInstance().LOGI('isLooping---' + Looping); } } this.mIjkMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); let mOnSeekCompleteListener: OnSeekCompleteListener = { onSeekComplete: () => { LogUtils.getInstance().LOGI("OnSeekCompleteListener-->go"); console.info('onecold startPlayOrResumePlay 10643') that.startPlayOrResumePlay(); } } this.mIjkMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener); let mOnInfoListener: OnInfoListener = { onInfo: (what: number, extra: number) => { LogUtils.getInstance().LOGI("OnInfoListener-->go:" + what + "===" + extra); that.hideLoadIng(); } } this.mIjkMediaPlayer.setOnInfoListener(mOnInfoListener); let mOnErrorListener: OnErrorListener = { onError: (what: number, extra: number) => { this.stopProgressTask(); LogUtils.getInstance().LOGI("OnErrorListener-->go:" + what + "===" + extra + " this.videoUrl=" + this.videoUrl) that.hideLoadIng(); if (StrUtil.isNotEmpty(this.videoUrl) && !FileUtil.accessSync(this.videoUrl)) { ToastUtil.showToast(Utility.resourceToString(getContext(), $r('app.string.file_not_exist'))) } else { ToastUtil.showToast(getContext() .resourceManager .getStringByNameSync("Honey_the_video_is_playing_errant_The_system_is_wandering")) } } } this.mIjkMediaPlayer.setOnErrorListener(mOnErrorListener); this.mIjkMediaPlayer.setMessageListener(); this.mIjkMediaPlayer.prepareAsync(); this.mIjkMediaPlayer.start(); } //保存最后播放的那首歌和已经对应的播放列表 saveLastPlayList() { PreferencesUtil.putSync('LastMusicInfo', this.currentSong) PreferencesUtil.putSync('LastMusicList', this.songList) } // 清空播放记录 clearVideoHistory() { this.table.clearPlayHistory((success: boolean, error?: string) => { if (success) { this.historyList = []; this.updateListData(this.historyList) // PreferencesUtil.putSync(LocalMusic.HISTORY_MUSIC, this.historyList) ToastUtil.showToast('清空播放记录成功') } else { console.error(" onecold 清空播放记录数据库失败原因: " + error); } }); } public async setAvSessionListener() { if (!this.avSessionController) { return; } this.avSessionController.getAvSession()?.on('play', this.sessionPlayCallback); this.avSessionController.getAvSession()?.on('pause', this.sessionPauseCallback); this.avSessionController.getAvSession()?.on('stop', this.sessionStopCallback); this.avSessionController.getAvSession()?.on('playNext', this.sessionPlayNextCallback); this.avSessionController.getAvSession()?.on('playPrevious', this.sessionPlayPreviousCallback); this.avSessionController.getAvSession()?.on('fastForward', this.sessionFastForwardCallback); this.avSessionController.getAvSession()?.on('rewind', this.sessionRewindCallback); this.avSessionController.getAvSession()?.on('seek', this.sessionSeekCallback); this.avSessionController.getAvSession()?.on('setLoopMode', this.sessionSetLoopModeCallback); this.avSessionController.getAvSession()?.on('toggleFavorite', this.sessionToggleFavoriteCallback); this.avSessionController.getAvSession()?.on('outputDeviceChange', this.sessionOutputDeviceChange) } setLoopMode() { let itype = this.playType + 1 if (itype >= 4) { this.playType = 0 ToastUtil.showToast('连续播放') } else if (itype === 1) { this.playType = 1 ToastUtil.showToast('单曲循环') } else if (itype === 2) { this.playType = 2 ToastUtil.showToast('单曲播完') } else if (itype === 3) { this.playType = 3 ToastUtil.showToast('随机播放') } else if (itype === 0) { this.playType = 0 ToastUtil.showToast('连续播放') } PreferencesUtil.putSync('musicPlayType', this.playType) this.setCurrentPlayMode() } private sessionSetLoopModeCallback = (mode: number) => { if (this.mIjkMediaPlayer != null) { ToastUtil.showToast('当前播放模式:' + mode) Logger.info('onecold 当前播放模式= ' + mode) let hmPlayMode = mode + 1 if (hmPlayMode >= 4) { hmPlayMode = 0 } if (hmPlayMode === 1) { this.playType = 1 ToastUtil.showToast('单曲循环') } else if (hmPlayMode === 0) { this.playType = 2 ToastUtil.showToast('单曲播完') } else if (hmPlayMode === 3) { this.playType = 3 ToastUtil.showToast('随机播放') } else if (hmPlayMode === 2) { this.playType = 0 ToastUtil.showToast('连续播放') } PreferencesUtil.putSync('musicPlayType', this.playType) // 应用收到设置循环模式的指令后,应用自定下一个模式,切换完毕后通过AVPlaybackState上报切换后的LoopMode。 let playBackState: avSession.AVPlaybackState = { loopMode: hmPlayMode, }; this.avSessionController.getAvSession()?.setAVPlaybackState(playBackState).then(() => { console.info(`set setLoopMode AVPlaybackState successfully`); }).catch((err: BusinessError) => { console.error(`Failed to setLoopMode set AVPlaybackState. Code: ${err.code}, message: ${err.message}`); }); } } setCurrentPlayMode() { if (!this.avSessionController) { return; } let mLoopMode = 0 switch (this.playType) { case 0: mLoopMode = avSession.LoopMode.LOOP_MODE_LIST //连续播放 break; case 1: mLoopMode = avSession.LoopMode.LOOP_MODE_SINGLE //单曲 break; case 2: mLoopMode = avSession.LoopMode.LOOP_MODE_SEQUENCE //正常播放 break; case 3: mLoopMode = avSession.LoopMode.LOOP_MODE_SHUFFLE //随机播放 break; } // 应用启动时/内部切换循环模式,需要把应用内的当前的循环模式设置给AVSession。 let playBState: avSession.AVPlaybackState = { loopMode: mLoopMode, isFavorite: Utility.getIsFav(this.favList, this.currentSong), }; this.avSessionController.getAvSession()?.setAVPlaybackState(playBState).then(() => { console.info(`set setLoopMode AVPlaybackState successfully`); }).catch((err: BusinessError) => { console.error(`Failed to setLoopMode set AVPlaybackState. Code: ${err.code}, message: ${err.message}`); }); } private sessionToggleFavoriteCallback = (assetId: string) => { if (this.mIjkMediaPlayer != null && this.currentSong) { console.info(`on toggleFavorite `); // 应用收到收藏命令,进行收藏处理。 this.isFac = Utility.getIsFav(this.favList, this.currentSong) this.doFav(this.currentSong) // 应用内完成或者取消收藏,把新的收藏状态设置给AVSession。 let playbackState: avSession.AVPlaybackState = { isFavorite: !this.isFac, }; this.avSessionController.getAvSession()?.setAVPlaybackState(playbackState).then(() => { console.info(`SetAVPlaybackState successfully`); }).catch((err: BusinessError) => { console.info(`SetAVPlaybackState BusinessError: code: ${err.code}, message: ${err.message}`); }); } } private sessionOutputDeviceChange = async (connectState: avSession.ConnectionState, device: avSession.OutputDeviceInfo) => { let currentDevice: avSession.DeviceInfo = device?.devices?.[0]; if (currentDevice.castCategory === avSession.AVCastCategory.CATEGORY_REMOTE && connectState === avSession.ConnectionState.STATE_CONNECTED) { await this.startCasting(); } else if ((currentDevice.castCategory === avSession.AVCastCategory.CATEGORY_REMOTE && connectState === avSession.ConnectionState.STATE_DISCONNECTED) || (currentDevice.castCategory === avSession.AVCastCategory.CATEGORY_LOCAL && connectState === avSession.ConnectionState.STATE_CONNECTED)) { if (device) { this.endCasting(); } } }; private async startCasting() { this.castController = await this.avSessionController.getAvSession()?.getAVCastController(); this.setPlaybackStateChangeListener(); this.pause(); this.initQueueItem(); this.prepare(); let refToIsCasting: AbstractProperty | undefined = AppStorage.ref('isCasting'); refToIsCasting?.set(true); this.castSeek = true; this.isCastPlaying = true; } async changeCasting() { this.castController = await this.avSessionController.getAvSession()?.getAVCastController(); this.initQueueItem(); this.prepare(); this.setPlaybackStateChangeListener(); this.isCastPlaying = true; this.castSeek = true; } public endCasting() { this.avSessionController.getAvSession()?.stopCasting(); this.stopCast(); this.isCastPlaying = false; let refToIsCasting: AbstractProperty | undefined = AppStorage.ref('isCasting'); refToIsCasting?.set(false); this.pause(); this.setIsPlaying(false); // if (this.curIndex === this.curSource.index) { // this.play // this.seek(this.currentTime * 1000); // this.setIsPlaying(true); // } else { // this.pauseVideo(); // this.setIsPlaying(false); // } } public setIsPlaying(isPlayer: boolean) { this.isPlaying = isPlayer; // 发送播放状态变化事件给歌单详情页面 try { const eventPlaybackStatus: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYBACK_STATUS } const currentFilePath = this.currentSong?.filePath || '' const eventData: emitter.EventData = { data: { isPlaying: this.isPlaying, curIndex: this.curIndex, currentFilePath: currentFilePath } }; emitter.emit(eventPlaybackStatus, eventData) Logger.info(`heanup 发送播放状态变化事件: isPlaying=${this.isPlaying}, curIndex=${this.curIndex}, currentFilePath=${currentFilePath}`) } catch (error) { Logger.error('heanup 发送播放状态变化事件失败: ' + error) } } private setPlaybackStateChangeListener(): void { this.castController?.on('playbackStateChange', 'all', this.playbackStateChangeListener); this.castController?.on('endOfStream', this.reloadCasting); this.castController?.on('playNext', this.playNextCallback); this.castController?.on('playPrevious', this.playPreviousCallback); } private updateSessionPlayState(isPlay: boolean): void { // if(!this.isBgPlayOpen) // return Logger.info(TAG, `updateIsPlay isPlay: ${isPlay}`); this.setAVSessionPlayState({ state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE, position: { elapsedTime: this.mIjkMediaPlayer.getCurrentPosition(), updateTime: new Date().getTime() } }); } private setAVSessionPlayState(playbackState: avSession.AVPlaybackState): void { this.avSessionController.setAvSessionPlayState(playbackState) } private positionChange(position: number) { this.currentTime2 = position / 1000; this.currentStringTime = secondToTime(Math.floor(position / 1000)); } private playDurationChange(duration: number) { this.duration = duration; this.durationTime = Math.floor(this.duration / 1000); this.durationStringTime = secondToTime((this.durationTime)); } private playbackStateChangeListener = (playbackState: avSession.AVPlaybackState) => { const duration = playbackState?.extras?.duration; if (typeof duration === 'number') { this.playDurationChange(duration as number); } if (typeof playbackState?.position?.elapsedTime !== 'undefined') { this.positionChange(playbackState?.position?.elapsedTime); } if (playbackState.state === CommonConstants2.PLAYBACK_STATE_PAUSE || playbackState.state === CommonConstants2.PLAYBACK_STATE_STOP || playbackState.state === CommonConstants2.PLAYBACK_STATE_PREPARE || playbackState.state === CommonConstants2.PLAYBACK_STATE_INITIAL) { this.isCastPlaying = false; } if (playbackState.state === CommonConstants2.PLAYBACK_STATE_PLAY) { this.isCastPlaying = true; } }; private reloadCasting = async () => { let item = this.songList[this.curIndex]; let uri = fileUri.getUriFromPath(item.filePath); let file = fs.openSync(uri, fs.OpenMode.READ_ONLY); // description.startPosition = 0; this.castItem = { itemId: this.curIndex, description: { assetId: item.filePath, title: item.name, artist: '', // 网络资源投播,设置mediaUri; 本地资源投播,将本地文件打开后,相关的文件描述符设置到fdSrc // mediaUri: item.filePath, fdSrc: { fd: file.fd }, // 该字段大写,音频'AUDIO',视频'VIDEO' mediaType: 'VIDEO', mediaSize: item.videoSize, //startPosition为投播当前进度,设置该字段可将本机播放进度同步到远端 startPosition: this.mIjkMediaPlayer.getCurrentPosition(), // 投播资源播放时长,设置该字段可将本机播放时长同步到远端显示 duration: this.duration, // albumCoverUri: 'https://www.example.jpeg', // albumTitle: '《ExampleAlbum》', appName: this.appName, // DRM资源,需要配置支持的DRM类型, 以chinaDRM为例。 // drmScheme: '3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c', } }; this.prepare(); this.isCastPlaying = true; }; public playNextCallback = (): void => { this.playNext() }; public playPreviousCallback = (): void => { this.playPrevious() }; async prepare() { await this.castController?.prepare(this.castItem); await this.castController?.start(this.castItem); } public initQueueItem() { let item = this.songList[this.curIndex]; let time = this.currentTime; let uri = fileUri.getUriFromPath(item.filePath); let file = fs.openSync(uri, fs.OpenMode.READ_ONLY); this.castItem = { itemId: this.curIndex, description: { assetId: item.filePath, title: item.name, artist: '', // 网络资源投播,设置mediaUri; 本地资源投播,将本地文件打开后,相关的文件描述符设置到fdSrc fdSrc: { fd: file.fd }, // 该字段大写,音频'AUDIO',视频'VIDEO' mediaType: 'AUDIO', mediaSize: item.videoSize, //startPosition为投播当前进度,设置该字段可将本机播放进度同步到远端 startPosition: this.mIjkMediaPlayer.getCurrentPosition(), // 投播资源播放时长,设置该字段可将本机播放时长同步到远端显示 duration: this.duration, // albumCoverUri: 'https://www.example.jpeg', // albumTitle: '《ExampleAlbum》', appName: this.appName, // DRM资源,需要配置支持的DRM类型, 以chinaDRM为例。 // drmScheme: '3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c', } }; } async stopCast() { try { this.castController?.off('playbackStateChange'); this.castController?.off('endOfStream'); this.castController?.off('playNext'); this.castController?.off('playPrevious'); this.castController?.off('error'); } catch (error) { hilog.info(0x0000, TAG, `Failed to setAVCastController. code: ${error.code}, message: ${error.message}`); } } private sessionPlayCallback = (): void => { console.info('onecold playOrPause 11050') this.playOrPause() }; private sessionPauseCallback = (): void => { console.info('onecold playOrPause 11054') this.pause() }; private playOrPause() { if (!this.debounce()) { return; } if (this.CONTROL_PlayStatus === PlayStatus.PLAY) { this.pause(); } else { console.info('onecold startPlayOrResumePlay 11064') this.startPlayOrResumePlay(); this.playChange() } } private sessionPlayNextCallback = (): void => { this.playNext() }; private sessionPlayPreviousCallback = (): void => { this.playPrevious() }; private sessionStopCallback = (): void => { this.stop() }; private sessionFastForwardCallback = (time?: number) => { if (!time) { return; } if (this.mIjkMediaPlayer != null) { const curPosition = this.mIjkMediaPlayer.getCurrentPosition() let seeTime = curPosition + time * 1000; // 限制 seekValue 在合法范围内 if (seeTime < 0) { seeTime = 0; } else if (seeTime > this.duration) { seeTime = this.duration; } Logger.info('onecold seeTime= ' + seeTime) this.setSeekToActionProgress(seeTime) this.seekTo(seeTime + "") } }; /** * Gesture method onActionUpdate. * * @param event Gesture event.手势拖动设置快进和后退 */ private setSeekToActionProgress(position: number) { // let position = this.mIjkMediaPlayer.getCurrentPosition(); let duration = this.mIjkMediaPlayer.getDuration(); let pos = 0; if (duration > 0) { this.slideEnable = true; let curPercent = position / duration; pos = curPercent * 100; if (pos > this.PROGRESS_MAX_VALUE) { this.progressValue = this.PROGRESS_MAX_VALUE } else { this.progressValue = pos; } } // LogUtils.getInstance() // .LOGI("setProgress position:" + position + ",duration:" + duration + ",progressValue:" + pos); this.totalTime = this.stringForTime(duration); if (position > duration) { position = duration; } this.isCurrentTime = true; this.currentTime = this.stringForTime(position); this.isCurrentTime = false } private sessionRewindCallback = (time?: number) => { if (!time) { return; } if (this.mIjkMediaPlayer != null) { const curPosition = this.mIjkMediaPlayer.getCurrentPosition() let seeTime = curPosition - time * 1000; // 限制 seekValue 在合法范围内 if (seeTime < 0) { seeTime = 0; } else if (seeTime > this.duration) { seeTime = this.duration; } Logger.info('onecold seeTime= ' + seeTime) this.setSeekToActionProgress(seeTime) this.seekTo(seeTime + "") } }; private sessionSeekCallback = (seekTime: number) => { if (this.mIjkMediaPlayer != null) { const curPosition = this.mIjkMediaPlayer.getCurrentPosition() this.seekTo(seekTime + "") } }; private async getFileSize(filePath: string): Promise { try { const stat = await fs.stat(filePath); return stat.size; } catch (err) { console.error('Failed to get file size: ' + err.message); return 0; } } private pause() { if (this.mIjkMediaPlayer.isPlaying()) { this.savePlaybackPosition(); this.mIjkMediaPlayer.pause(); this.setProgress(); this.mDestroyPage = true; this.CONTROL_PlayStatus = PlayStatus.PAUSE; this.updateSessionPlayState(false) this.playChange() if (this.pipController) { this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE, PiPWindow.PiPControlStatus.PAUSE); } } } private stop() { this.savePlaybackPosition(); this.CONTROL_PlayStatus = PlayStatus.INIT; this.mIjkMediaPlayer.stop(); this.mIjkMediaPlayer.release(); this.stopProgressTask(); this.updateSessionPlayState(false) this.playChange() this.watchStatus(); } //保持记忆播放功能 private savePlaybackPosition() { if (this.mIjkMediaPlayer != null) { const position = this.mIjkMediaPlayer.getCurrentPosition(); // ToastUtil.showToast('保存记忆播放 = ' + position) const duration = this.mIjkMediaPlayer.getDuration(); const threshold = 5000; // 阈值,单位为毫秒(这里设为5秒) // 如果播放位置接近视频末尾,则保存 position 为 0 const playbackPosition = (duration - position < threshold) ? 0 : position; PreferencesUtil.putSync(this.videoUrl, playbackPosition); } } //获取记忆播放功能 private restorePlaybackPosition() { const position: number = PreferencesUtil.getNumberSync(this.videoUrl, 0); // ToastUtil.showToast('position = ' + position) if (this.mIjkMediaPlayer != null && position > 0) { // ToastUtil.showToast('seekTo = ' + position) this.seekTo(position + ""); } } private seekTo(value: string) { if (StrUtil.isNotEmpty(this.videoUrl) && this.videoUrl.toLowerCase().endsWith('.wma')) { ToastUtil.showToast('wma格式不支持拖动快进。') return } this.isSeekTo = true this.mIjkMediaPlayer.seekTo(value); console.info('onecold startPlayOrResumePlay 11227') // this.startPlayOrResumePlay() // 在这里恢复计时器状态 this.setProgress() this.isSeekTo = false } // 添加到下一首播放 private addToNextPlay(song: VideoItem) { if (!this.debounce()) { return; } if (this.currentSong) { if (this.currentSong === song) { ToastUtil.showToast('当前已经在播放这首歌了!') return } } // 如果未开始播放或队列为空 if (this.curIndex === -1 || this.songList.length === 0) { this.songList = [song]; this.curIndex = 0; this.doPlay(song) return; } // 插入到当前索引+1的位置 const insertPos = this.curIndex + 1; this.songList.splice(insertPos, 0, song); this.songList = [...this.songList]; // 触发状态更新 this.sonDataSource.pushArrayData(this.songList) ToastUtil.showToast('已添加至下一首播放') } //后退10s,可再设置设置默认几秒 backForward() { if(this.mIjkMediaPlayer){ let pos = this.mIjkMediaPlayer.getCurrentPosition(); let value = pos - Number(this.fastForwardSeconds) * 1000; this.seekTo(value+""); } } //快进10s,可再设置设置默认几秒 fastForward() { if(this.mIjkMediaPlayer){ let pos = this.mIjkMediaPlayer.getCurrentPosition(); let value = pos + Number(this.fastForwardSeconds) * 1000; this.seekTo(value+""); } } //下一个 private playNext() { if (!this.debounce()) { return; } if (this.playType == 3) { //3:随机播放 this.randomPlay() return; } if (ArrayUtil.isNotEmpty(this.songList)) { if (this.curIndex == this.songList.length - 1) { this.curIndex = 0; } else { this.curIndex++; } this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = this.songList[this.curIndex] this.videoUrl = this.songList[this.curIndex].filePath; this.artist = this.songList[this.curIndex].artist this.name = this.songList[this.curIndex].name //this.cover = this.songList[this.curIndex].pixelMapPath this.changeImageAnimation() } } changeImageAnimation() { if(this.openSkipSongAnimate){ // 第一步:淡出动画 this.getUIContext()?.animateTo({ duration: 500, curve: Curve.EaseOut, }, () => { this.opacityValueImage = 0.1; if(this.isCoverRectangle){ this.scaleValueImage = 0.88 } }); // 第二步:切换图片后淡入 setTimeout(() => { this.cover = this.songList[this.curIndex].pixelMapPath this.getUIContext()?.animateTo({ duration: 500, curve: Curve.EaseIn, }, () => { this.opacityValueImage = 1; this.scaleValueImage = 1 // 图标恢复到原始大小 this.startPlayOrResumePlay() }); }, 500); }else{ this.cover = this.songList[this.curIndex].pixelMapPath this.startPlayOrResumePlay() } } // 存储已播放的歌曲索引 private playedIndices: Set = new Set(); //随机播放 private randomPlay() { // if (!this.debounce()) { // return; // } if (ArrayUtil.isNotEmpty(this.songList)) { if (this.songList.length > 3) { // 如果所有歌曲都播放过一次,清空已播放索引集合 if (this.playedIndices.size === this.songList.length) { this.playedIndices.clear(); } // 随机选择一个未播放的歌曲索引 let newIndex: number; // 添加超时机制防止无限循环 let attempt = 0; const maxAttempts = 100; // 根据需求调整 do { newIndex = RandomUtil.getRandomNumber(0, this.songList.length - 1); attempt++; } while (this.playedIndices.has(newIndex) && attempt < maxAttempts); if (attempt >= maxAttempts) { // 处理无法找到新索引的情况(如随机选择或按顺序播放) newIndex = Math.floor(Math.random() * this.songList.length); } this.curIndex = newIndex; this.playedIndices.add(newIndex); } else { // 当歌曲数量小于或等于3时,直接播放下一首 if (this.curIndex == this.songList.length - 1) { this.curIndex = 0; } else { this.curIndex++; } } this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = this.songList[this.curIndex]; this.videoUrl = this.songList[this.curIndex].filePath; this.name = this.songList[this.curIndex].name; this.artist = this.songList[this.curIndex].artist // this.cover = this.songList[this.curIndex].pixelMapPath this.changeImageAnimation() } } private playIndex(index: number) { if (!this.debounce()) { return; } if (ArrayUtil.isNotEmpty(this.songList)) { this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = this.songList[this.curIndex] this.videoUrl = this.songList[index].filePath; this.name = this.songList[index].name this.artist = this.songList[this.curIndex].artist this.curIndex = index; this.changeImageAnimation() } } //上一个 private playPrevious() { if (!this.debounce()) { return; } if (this.playType == 3) { //3:随机播放的上一首应该应该播放历史记录的第二首 this.randomModePlayFromHistory() return; } if (this.curIndex == 0) { this.curIndex = this.songList.length - 1; } else { this.curIndex--; } this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = this.songList[this.curIndex] this.videoUrl = this.songList[this.curIndex].filePath; this.name = this.songList[this.curIndex].name this.artist = this.songList[this.curIndex].artist this.changeImageAnimation() } //随机播放模式下点击上一首: 上一首应该应该播放历史记录的第二首 private randomModePlayFromHistory() { if (this.historyList.length >= 2) { // 播放历史记录的第二首 const prevSong = this.historyList[1]; this.curIndex = this.songList.findIndex(song => song.filePath === prevSong.filePath); this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = prevSong; this.videoUrl = prevSong.filePath; this.cover = prevSong.pixelMapPath this.artist = prevSong.artist this.name = prevSong.name; console.info('onecold startPlayOrResumePlay 11434') this.startPlayOrResumePlay(); } else { // 如果历史记录不足两首,可根据需求处理,这里简单按普通逻辑处理 if (this.curIndex === 0) { this.curIndex = this.songList.length - 1; } else { this.curIndex--; } this.CONTROL_PlayStatus = PlayStatus.INIT; this.stop(); this.currentSong = this.songList[this.curIndex]; this.videoUrl = this.songList[this.curIndex].filePath; this.artist = this.songList[this.curIndex].artist this.name = this.songList[this.curIndex].name; this.changeImageAnimation() } } debounce() { const delay = 600; let cur = new Date().getTime(); if (cur - this.last > delay) { this.last = cur; return true; } return false; } /** * Sets whether the screen is a constant based on the playback status. */ watchStatus() { // let windowClass = GlobalContext.getContext().getObject('windowClass') as window.Window; if (this.CONTROL_PlayStatus === PlayStatus.PLAY) { this.windowClass.setWindowKeepScreenOn(true); } else { this.windowClass.setWindowKeepScreenOn(false); } } /** * Volume gesture method onActionStart. * * @param event Gesture event. */ private customDialogComponentIdMM: number = 0 @Builder customDialogComponentMM() { Column() { Row() { Blank() Text('验证密码') .fontSize(22) .fontColor($r('app.color.pri_bg')) .margin({ top: 18 }) Blank() Image($r('app.media.close')) .fillColor(Color.White) .margin({ top: 18, right: 18 }) .width(20) .onClick(() => { promptAction.closeCustomDialog(this.customDialogComponentIdMM) }) } .width('100%') // 使Row占据整个宽度 .justifyContent(FlexAlign.SpaceBetween); // 在Row中创建空间 Verify({ onVerifyCompleted: this.handleVerifyCompleted.bind(this) }); }.height('60%') .backgroundColor($r('app.color.index_background')) .justifyContent(FlexAlign.SpaceBetween) } private handleVerifyCompleted(isSuccess: boolean) { if (isSuccess) { console.log('验证成功'); // 在这里处理验证成功后的逻辑 promptAction.closeCustomDialog(this.customDialogComponentIdMM) this.currentPath = this.lockPath this.getSortedFiles(this.currentPath) } else { // console.log('验证失败'); ToastUtil.showToast('验证失败') } } private showVerifyDialog() { promptAction.openCustomDialog({ transition: TransitionEffect.asymmetric( TransitionEffect.OPACITY.animation({ duration: 888 }).combine( TransitionEffect.translate({ y: 888 }).animation({ duration: 888 })) , TransitionEffect.OPACITY.animation({ delay: 888, duration: 888 }).combine( TransitionEffect.translate({ y: 888 }).animation({ duration: 888 })) ), builder: () => { this.customDialogComponentMM() }, isModal: true, autoCancel: true, maskColor: Color.Transparent, alignment: DialogAlignment.Bottom, onWillDismiss: (dismissDialogAction: DismissDialogAction) => { console.info("reason" + JSON.stringify(dismissDialogAction.reason)) console.log("dialog onWillDismiss") if (dismissDialogAction.reason == DismissReason.PRESS_BACK) { dismissDialogAction.dismiss() } if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) { dismissDialogAction.dismiss() } } }).then((dialogId: number) => { this.customDialogComponentIdMM = dialogId }) } //左滑操作 移动文件 重命名 删除等操作 @Builder DeleteButton(item: VideoItem, index: number, filePath: string) { Row() { //加入收藏 Button() { Image(Utility.getIsFav(this.favList, item) ? $r('app.media.add_fac_light2') : $r('app.media.add_fac')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { if (item) { this.doFav(item) } }) //加入下一首播放 Button() { Image($r('app.media.next_to_play')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { this.addToNextPlay(item); }) //复制文件 Button() { Image($r('app.media.copy')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .id(filePath + 'copy') .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { this.showCopyDialog(false, item, index, filePath + 'copy'); }) //移动文件 Button() { Image($r('app.media.cut')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .id(filePath) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { this.showCutDialog(false, item, index, filePath); }) Button() { Image($r('app.media.cut_current')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .id(filePath + 'current') .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : (this.isHasDir ? Visibility.Visible : Visibility.None)) .onClick(() => { this.showCutDialog(true, item, index, filePath + 'current'); }) //重命名 Button() { Image($r('app.media.rename2')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? (item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ? Visibility.None : Visibility.Visible) : Visibility.Visible) .margin(5) .onClick(() => { this.showReNameDialog(item, index + '', filePath) }) Button() { Image($r('app.media.delete2')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? (item.name === LocalMusic.STR_LOCK_VIDEO || item.name === LocalMusic.STR_FAC_VIDEO || item.name === LocalMusic.STR_HISTORY_MUSIC ? Visibility.None : Visibility.Visible) : Visibility.Visible) .margin(5) .onClick(() => { this.selectedFiles.push(item) this.showWarnIsDelete() // this.showWarnIsDeleteFile(item, index + '', filePath) }) Button() { Image($r('app.media.share2')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle) .backgroundColor(this.themeColor) .margin(5) .visibility(item.type === CommonConstants.TYPE_IS_DIR ? Visibility.None : Visibility.Visible) .onClick(() => { Utility.doShareMusic(item, getContext(this) as common.UIAbilityContext) }) } } /** * 穿山甲banner广告 */ // private declare bannerAd: CSJNativeExpressAd; // @State private status: string = "未加载" // @State private isShowAd: boolean = false // private declare mAdSlot: AdSlot; // private mBiddingAdm = '' //服务端bidding才需要设置 // private expressLoadAdListener: NativeExpressAdListener = { // /** // * 加载失败的回调 // * // * @param code // * @param message // */ // onError: (code: number, message: string) => { // console.error("加载广告失败,code=" + code + ",message=" + message); // this.status = "加载广告失败,code=" + code + ",message=" + message; // }, // // /** // * 广告加载成功的回调,接入方可以在这个回调中进行渲染 // * // * @param ads 返回的广告列表 // */ // // onNativeExpressAdLoad: (ads: ArrayList) => { // console.log("BannerExpressAdPage==onNativeExpressAdLoad......success"); // if (ads && ads.length > 0) { // ads.forEach((ad: CSJNativeExpressAd, idx: number) => { // ad.setExpressInteractionListener({ // /** // *广告的点击回调 // * @param type 广告的交互类型 // */ // onAdClicked: (type: number) => { // console.log("BannerExpressAdPage==onAdClicked......"); // }, // // /** // * 广告的展示回调 每个广告仅回调一次 // * @param type 广告的交互类型 // */ // onAdShow: (type: number) => { // console.log("BannerExpressAdPage==onAdShow......"); // }, // // /** // * 模板渲染失败 // */ // onRenderFail: (code: number, msg: string) => { // console.log("BannerExpressAdPage==onRenderFail...code=" + code + ",msg=" + msg); // }, // // /** // * 模板渲染成功 // * @param width 返回view的宽 单位 vp // * @param height 返回view的高 单位 vp // */ // onRenderSuccess: (width: number, height: number) => { // console.log("BannerExpressAdPage==onRenderSuccess......width=" + width + ",height=" + height); // this.bannerAd = ad; // this.status = "广告加载完成待展示"; // // this.showBannerAd() // } // }) // //渲染广告 // ad.setSlideIntervalTime(30 * 1000); //设置轮播时间长 // this.setDislikeCallback(ad); //设置dislike // ad.render(this.getUIContext()) // this.status = "广告加载中..."; // }); // } // } // } // // private setDislikeCallback(ad: CSJNativeExpressAd) { // ad.setDislikeCallback({ // /** // * dislike show // */ // onShow: () => { // console.log("BannerExpressAdPage==dislike......show"); // }, // // /** // * @param position 选择的位置 // * @param value 选择的内容 // * @param enforceRemove 是否强制关闭广告 // */ // onSelected: (position: number, value: string, enforceRemove: boolean) => { // this.isShowAd = false; // console.log("BannerExpressAdPage==dislike......onSelected:position=" + position + ",value:" + value + // ",enforce=" + enforceRemove); // }, // // /** // * 点击取消 // */ // onCancel: () => { // console.log("BannerExpressAdPage==dislike......onCancel"); // } // // }) // } /** * 加载Banner广告 */ loadBannerAd(rit: string) { // if (Utility.isNoble()) { // return // } // if (!Utility.isPassInstallTime(2)) { // return // } // this.isShowAd = false; // let adCreator: CSJAdCreator = CSJAdSdk.getAdCreator() // this.mAdSlot = new AdSlotBuilder() // .setCodeId(rit) // .setAcceptSize(CSJUtil.BANNER_WIGTH, CSJUtil.BANNER_HEIGHT) // .setAdCount(1) // .setBidAdm(this.mBiddingAdm) // .build() // PrintBiddingTokenUtils.printBiddingToken(this.mAdSlot, adCreator); // adCreator.loadBannerAd(this.mAdSlot, this.expressLoadAdListener) // this.status = "广告加载中..." } /** * 展示Banner广告 */ // showBannerAd() { // if (!this.bannerAd) { // return // } // this.isShowAd = true // this.status = "已展示" // } /** * 穿山甲广告代码结束 */ /** * 处理歌单播放请求 */ private handlePlaylistPlayRequest(playlistId: string, playlistName: string, songFilePaths: string[], startIndex: number) { try { Logger.info(`heanup 处理歌单播放请求: ${playlistName}, 歌曲数量: ${songFilePaths.length}, 开始索引: ${startIndex}`) // 从数据库重新加载这些歌曲,使用索引记录位置以保持顺序 const songMap: Map = new Map() let completedQueries = 0 Logger.info(`heanup 开始从数据库查询 ${songFilePaths.length} 首歌曲`) // 遍历所有文件路径,使用索引记录位置 for (let i = 0; i < songFilePaths.length; i++) { const filePath = songFilePaths[i] const index = i Logger.info(`heanup 正在查询歌曲[${index}]: ${filePath}`) // 使用已初始化的MediaTable实例从数据库查询歌曲信息 const queryPromise = this.table.queryVideoByFilePath(filePath) Logger.info(`heanup 创建了查询Promise,开始等待结果[${index}]: ${filePath}`) queryPromise.then((videoItem) => { completedQueries++ Logger.info(`heanup 查询Promise返回结果[${index}]: ${filePath}, 找到歌曲: ${videoItem ? videoItem.name : 'null'}`) if (videoItem) { // 使用索引作为key保存到Map中,保持原始顺序 songMap.set(index, videoItem) Logger.info(`heanup 从数据库找到歌曲[${index}]: ${videoItem.name} (${completedQueries}/${songFilePaths.length})`) } else { Logger.warn(`heanup 数据库中未找到歌曲[${index}]: ${filePath} (${completedQueries}/${songFilePaths.length})`) } // 检查是否所有歌曲都已加载完成 if (completedQueries === songFilePaths.length) { Logger.info(`heanup 所有数据库查询完成,共找到 ${songMap.size} 首歌曲`) // 按照索引顺序重建歌曲数组 const songs: VideoItem[] = [] for (let j = 0; j < songFilePaths.length; j++) { const song = songMap.get(j) if (song) { songs.push(song) Logger.info(`heanup 按顺序添加歌曲[${j}]: ${song.name}`) } } Logger.info(`heanup 最终歌曲列表顺序: ${songs.map((s, idx) => `[${idx}]${s.name}`).join(', ')}`) this.finishLoadingPlaylist(songs, startIndex, playlistName) } }).catch((error: Error) => { completedQueries++ Logger.error(`heanup 查询歌曲失败[${index}]: ${filePath}, 错误: ${error.message} (${completedQueries}/${songFilePaths.length})`) // 即使出错也要检查是否完成所有查询 if (completedQueries === songFilePaths.length) { Logger.info(`heanup 所有数据库查询完成(包含错误),共找到 ${songMap.size} 首歌曲`) // 按照索引顺序重建歌曲数组 const songs: VideoItem[] = [] for (let j = 0; j < songFilePaths.length; j++) { const song = songMap.get(j) if (song) { songs.push(song) } } this.finishLoadingPlaylist(songs, startIndex, playlistName) } }) } } catch (error) { Logger.error('heanup 处理简化歌单播放请求失败: ' + error) ToastUtil.showToast('播放失败') } } /** * 完成歌单加载并开始播放 */ private finishLoadingPlaylist(songs: VideoItem[], startIndex: number, playlistName: string) { if (songs.length === 0) { Logger.error('heanup 没有找到任何可播放的歌曲') ToastUtil.showToast('没有找到可播放的歌曲') return } this.songList = songs this.sonDataSource.pushArrayData(songs) this.sonDataSource.notifyDataReload() this.curIndex = startIndex if (songs[startIndex]) { // 确保当前播放的歌曲也更新到存储 AppStorage.setOrCreate('currentSong', songs[startIndex]) // 设置isFromSonPlayList=true,避免doPlay方法重新从全局列表构建播放列表 this.doPlay(songs[startIndex], startIndex, true) } PreferencesUtil.putSync('LastMusicList', this.songList) ToastUtil.showToast(`开始播放歌单: ${playlistName}`) } } //视频气泡窗口的布局 @Builder function customPopupBuilder(dataBu: BubbleBean) { List() { LazyForEach(new LazyDataSource(dataBu.data), (item: string, index: number) => { ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { Column() { Image(dataBu.imageRes[index]) .height(28) .fillColor(dataBu.color) .alignSelf(ItemAlign.Center) } .width(32) Text(item) .padding({ top: 10, bottom: 10, right: 10, left: 10 }) .fontColor(dataBu.modeType == index ? dataBu.color : $r('app.color.index_tab_font_color')) .textAlign(TextAlign.Start) .width("100%") } } .backgroundColor($r('app.color.settings_background_main')) .margin({ top: 6, bottom: 6 }) } .transition(TransitionEffect.asymmetric(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 500, curve: Curve.Ease, delay: 100 * index }), TransitionEffect.scale({ x: 0, y: 0 }))) // .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }), // TransitionEffect.scale({ x: 0, y: 0 }) )) .onClick(() => { dataBu.onItemClick?.(index) }) }) } .width(120) .divider({ strokeWidth: 1, color: "#22FFFFFF" }) } // Function to calculate a hash for the file list function simpleHash(fileList: string[]): string { let hash = 0; const str = fileList.join(','); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash |= 0; // Convert to 32bit integer } return hash.toString(); } interface MoreItem { id: number; image: Resource; title: string; } //视频气泡窗口的布局 @Builder function cutPopupBuilder(dataBu: BubbleBean) { Column() { Text(dataBu.isCopy ? '将文件复制到' : '将文件移动到') .padding({ top: 10, bottom: 10, right: 10, left: 10 }) .textAlign(TextAlign.Start) .width(160) .fontWeight(666) .fontSize(16) List() { ForEach(dataBu.data, (item: string, index: number) => { ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { Column() { Image($r('app.media.music_group')) .height(28) .fillColor(dataBu.color) .alignSelf(ItemAlign.Center) } .width(32) Text(item.startsWith('.') ? item.replace(/\./g, '') : item) .padding({ top: 10, bottom: 10, right: 10, left: 10 }) .textAlign(TextAlign.Start).width("100%")// .fontColor(Color.Gray) .fontSize(15) } } .backgroundColor(Color.Transparent) .margin({ top: 7, bottom: 7 }) } .transition(TransitionEffect.asymmetric(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) // .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }), // TransitionEffect.scale({ x: 0, y: 0 }) )) .onClick(() => { dataBu.onItemClick?.(index) }) }) } .width(150) .divider({ strokeWidth: 1, color: "#22FFFFFF" }) } .height(300) .padding({ bottom: 30 }) } function getFileDirName(filePath: string,rootPath:string): string{ if(filePath===rootPath){ return '首页' } let result = FileUtil.getFileName(filePath) if(StrUtil.isEmpty(result)) return '' if(result.startsWith('.')) result = result.replace(/\./g, '') return result }