PlaylistModel.ets 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import { VideoItem } from '../../viewmodel/VideoItem';
  2. import { PlayMode } from './PlayerStateModel';
  3. import { LogUtils } from '@ohos/ijkplayer';
  4. import { ArrayUtil, RandomUtil } from '@pura/harmony-utils';
  5. /**
  6. * 播放列表模型类
  7. * 管理播放列表和播放导航逻辑
  8. */
  9. export class PlaylistModel {
  10. private songs: VideoItem[] = [];
  11. private currentIndex: number = 0;
  12. private playedIndices: Set<number> = new Set(); // 用于随机播放的历史记录
  13. private playHistory: VideoItem[] = []; // 播放历史记录
  14. constructor(songs: VideoItem[] = [], currentIndex: number = 0) {
  15. this.songs = [];
  16. for (const song of songs) {
  17. this.songs.push(song);
  18. }
  19. this.currentIndex = Math.max(0, Math.min(currentIndex, songs.length - 1));
  20. LogUtils.getInstance().LOGI(`PlaylistModel: Initialized with ${songs.length} songs, index ${this.currentIndex}`);
  21. }
  22. /**
  23. * 获取歌曲列表副本
  24. */
  25. getSongs(): VideoItem[] {
  26. const result: VideoItem[] = [];
  27. for (const song of this.songs) {
  28. result.push(song);
  29. }
  30. return result;
  31. }
  32. /**
  33. * 获取当前播放索引
  34. */
  35. getCurrentIndex(): number {
  36. return this.currentIndex;
  37. }
  38. /**
  39. * 设置当前播放索引
  40. */
  41. setCurrentIndex(index: number): boolean {
  42. if (index >= 0 && index < this.songs.length) {
  43. this.currentIndex = index;
  44. LogUtils.getInstance().LOGI(`PlaylistModel: Current index set to ${index}`);
  45. return true;
  46. }
  47. LogUtils.getInstance().LOGI(`PlaylistModel: Invalid index ${index}, playlist size: ${this.songs.length}`);
  48. return false;
  49. }
  50. /**
  51. * 获取当前播放的歌曲
  52. */
  53. getCurrentSong(): VideoItem | null {
  54. if (this.isEmpty() || this.currentIndex < 0 || this.currentIndex >= this.songs.length) {
  55. return null;
  56. }
  57. return this.songs[this.currentIndex];
  58. }
  59. /**
  60. * 获取总歌曲数量
  61. */
  62. getTotalCount(): number {
  63. return this.songs.length;
  64. }
  65. /**
  66. * 检查播放列表是否为空
  67. */
  68. isEmpty(): boolean {
  69. return this.songs.length === 0;
  70. }
  71. /**
  72. * 播放列表操作:添加歌曲
  73. */
  74. addSong(song: VideoItem, index?: number): void {
  75. if (index !== undefined && index >= 0 && index <= this.songs.length) {
  76. this.songs.splice(index, 0, song);
  77. // 如果插入位置在当前播放位置之前,需要调整当前索引
  78. if (index <= this.currentIndex) {
  79. this.currentIndex++;
  80. }
  81. } else {
  82. this.songs.push(song);
  83. }
  84. LogUtils.getInstance().LOGI(`PlaylistModel: Song added, total count: ${this.songs.length}`);
  85. }
  86. /**
  87. * 播放列表操作:添加多首歌曲
  88. */
  89. addSongs(songs: VideoItem[], index?: number): void {
  90. if (songs.length === 0) return;
  91. if (index !== undefined && index >= 0 && index <= this.songs.length) {
  92. for (let i = 0; i < songs.length; i++) {
  93. this.songs.splice(index + i, 0, songs[i]);
  94. }
  95. // 如果插入位置在当前播放位置之前,需要调整当前索引
  96. if (index <= this.currentIndex) {
  97. this.currentIndex += songs.length;
  98. }
  99. } else {
  100. for (const song of songs) {
  101. this.songs.push(song);
  102. }
  103. }
  104. LogUtils.getInstance().LOGI(`PlaylistModel: ${songs.length} songs added, total count: ${this.songs.length}`);
  105. }
  106. /**
  107. * 播放列表操作:移除歌曲
  108. */
  109. removeSong(index: number): VideoItem | null {
  110. if (index < 0 || index >= this.songs.length) {
  111. LogUtils.getInstance().LOGI(`PlaylistModel: Invalid remove index ${index}`);
  112. return null;
  113. }
  114. const removedSong = this.songs.splice(index, 1)[0];
  115. // 调整当前播放索引
  116. if (index < this.currentIndex) {
  117. this.currentIndex--;
  118. } else if (index === this.currentIndex) {
  119. // 如果删除的是当前播放的歌曲,保持索引不变(播放下一首)
  120. // 但如果删除的是最后一首,需要调整到前一首
  121. if (this.currentIndex >= this.songs.length && this.songs.length > 0) {
  122. this.currentIndex = this.songs.length - 1;
  123. }
  124. }
  125. LogUtils.getInstance().LOGI(`PlaylistModel: Song removed at index ${index}, total count: ${this.songs.length}`);
  126. return removedSong;
  127. }
  128. /**
  129. * 播放列表操作:移动歌曲
  130. */
  131. moveSong(fromIndex: number, toIndex: number): boolean {
  132. if (fromIndex < 0 || fromIndex >= this.songs.length ||
  133. toIndex < 0 || toIndex >= this.songs.length ||
  134. fromIndex === toIndex) {
  135. LogUtils.getInstance().LOGI(`PlaylistModel: Invalid move indices from ${fromIndex} to ${toIndex}`);
  136. return false;
  137. }
  138. const song = this.songs.splice(fromIndex, 1)[0];
  139. this.songs.splice(toIndex, 0, song);
  140. // 调整当前播放索引
  141. if (fromIndex === this.currentIndex) {
  142. this.currentIndex = toIndex;
  143. } else if (fromIndex < this.currentIndex && toIndex >= this.currentIndex) {
  144. this.currentIndex--;
  145. } else if (fromIndex > this.currentIndex && toIndex <= this.currentIndex) {
  146. this.currentIndex++;
  147. }
  148. LogUtils.getInstance().LOGI(`PlaylistModel: Song moved from ${fromIndex} to ${toIndex}`);
  149. return true;
  150. } /**
  151. * 播放列表操作:清空播放列表
  152. */
  153. clear(): void {
  154. this.songs = [];
  155. this.currentIndex = 0;
  156. this.playedIndices.clear();
  157. this.playHistory = [];
  158. LogUtils.getInstance().LOGI('PlaylistModel: Playlist cleared');
  159. }
  160. /**
  161. * 播放列表操作:替换整个播放列表
  162. */
  163. replaceSongs(songs: VideoItem[], currentIndex: number = 0): void {
  164. // 记录调用栈信息以便调试
  165. const stack = new Error().stack || 'No stack available';
  166. const caller = stack.split('\n')[2] || 'Unknown caller';
  167. this.songs = [];
  168. for (const song of songs) {
  169. this.songs.push(song);
  170. }
  171. this.currentIndex = Math.max(0, Math.min(currentIndex, songs.length - 1));
  172. this.playedIndices.clear();
  173. this.playHistory = [];
  174. // 添加验证日志,包含调用源信息
  175. const currentSong = this.getCurrentSong();
  176. LogUtils.getInstance().LOGI(`PlaylistModel: Playlist replaced with ${songs.length} songs, index ${this.currentIndex}, current song: ${currentSong?.name || 'null'}, caller: ${caller.trim()}`);
  177. // 如果播放列表突然变小,记录更详细的信息
  178. if (songs.length <= 20) {
  179. LogUtils.getInstance().LOGI(`PlaylistModel: Small playlist detected (${songs.length} songs), caller stack: ${stack.substring(0, 500)}`);
  180. LogUtils.getInstance().LOGI(`PlaylistModel: First few songs: ${songs.slice(0, 5).map(s => s.name).join(', ')}`);
  181. }
  182. // 如果索引无效,记录警告
  183. if (currentIndex >= 0 && currentIndex < songs.length && this.currentIndex !== currentIndex) {
  184. LogUtils.getInstance().LOGI(`PlaylistModel: Warning - requested index ${currentIndex} adjusted to ${this.currentIndex}`);
  185. }
  186. }
  187. /**
  188. * 播放列表操作:随机打乱播放列表
  189. */
  190. shuffle(): void {
  191. if (this.songs.length <= 1) return;
  192. // 保存当前播放的歌曲
  193. const currentSong = this.getCurrentSong();
  194. // 打乱播放列表
  195. for (let i = this.songs.length - 1; i > 0; i--) {
  196. const j = Math.floor(Math.random() * (i + 1));
  197. const temp = this.songs[i];
  198. this.songs[i] = this.songs[j];
  199. this.songs[j] = temp;
  200. }
  201. // 重新找到当前播放歌曲的位置
  202. if (currentSong) {
  203. const newIndex = this.songs.findIndex(song => song.filePath === currentSong.filePath);
  204. if (newIndex !== -1) {
  205. this.currentIndex = newIndex;
  206. }
  207. }
  208. this.playedIndices.clear();
  209. LogUtils.getInstance().LOGI('PlaylistModel: Playlist shuffled');
  210. }
  211. /**
  212. * 导航操作:检查是否有下一首
  213. */
  214. hasNext(playMode: PlayMode = PlayMode.SEQUENCE): boolean {
  215. if (this.isEmpty()) return false;
  216. switch (playMode) {
  217. case PlayMode.SINGLE_REPEAT:
  218. return true; // 单曲循环总是有下一首(自己)
  219. case PlayMode.SEQUENCE:
  220. return this.currentIndex < this.songs.length - 1;
  221. case PlayMode.NORMAL:
  222. return this.currentIndex < this.songs.length - 1;
  223. case PlayMode.RANDOM:
  224. return this.playedIndices.size < this.songs.length; // 还有未播放的歌曲
  225. default:
  226. return false;
  227. }
  228. }
  229. /**
  230. * 导航操作:检查是否有上一首
  231. */
  232. hasPrevious(playMode: PlayMode = PlayMode.SEQUENCE): boolean {
  233. if (this.isEmpty()) return false;
  234. switch (playMode) {
  235. case PlayMode.SINGLE_REPEAT:
  236. return true; // 单曲循环总是有上一首(自己)
  237. case PlayMode.SEQUENCE:
  238. return this.currentIndex > 0;
  239. case PlayMode.NORMAL:
  240. return this.currentIndex > 0;
  241. case PlayMode.RANDOM:
  242. return this.playHistory.length > 1; // 有播放历史记录
  243. default:
  244. return false;
  245. }
  246. }
  247. /**
  248. * 导航操作:获取下一首歌曲
  249. */
  250. getNext(playMode: PlayMode = PlayMode.SEQUENCE): VideoItem | null {
  251. if (this.isEmpty()) return null;
  252. let nextIndex = this.currentIndex;
  253. switch (playMode) {
  254. case PlayMode.SINGLE_REPEAT:
  255. // 单曲循环,返回当前歌曲
  256. break;
  257. case PlayMode.SEQUENCE:
  258. // 顺序播放,循环到开头
  259. nextIndex = (this.currentIndex + 1) % this.songs.length;
  260. break;
  261. case PlayMode.NORMAL:
  262. // 正常播放,不循环
  263. if (this.currentIndex < this.songs.length - 1) {
  264. nextIndex = this.currentIndex + 1;
  265. } else {
  266. return null; // 已经是最后一首
  267. }
  268. break;
  269. case PlayMode.RANDOM:
  270. // 随机播放
  271. nextIndex = this.getRandomNextIndex();
  272. if (nextIndex === -1) return null;
  273. break;
  274. }
  275. return this.songs[nextIndex];
  276. }
  277. /**
  278. * 导航操作:获取上一首歌曲
  279. */
  280. getPrevious(playMode: PlayMode = PlayMode.SEQUENCE): VideoItem | null {
  281. if (this.isEmpty()) return null;
  282. let prevIndex = this.currentIndex;
  283. switch (playMode) {
  284. case PlayMode.SINGLE_REPEAT:
  285. // 单曲循环,返回当前歌曲
  286. break;
  287. case PlayMode.SEQUENCE:
  288. // 顺序播放,循环到末尾
  289. prevIndex = this.currentIndex === 0 ? this.songs.length - 1 : this.currentIndex - 1;
  290. break;
  291. case PlayMode.NORMAL:
  292. // 正常播放,不循环
  293. if (this.currentIndex > 0) {
  294. prevIndex = this.currentIndex - 1;
  295. } else {
  296. return null; // 已经是第一首
  297. }
  298. break;
  299. case PlayMode.RANDOM:
  300. // 随机播放,从历史记录获取
  301. return this.getRandomPrevious();
  302. }
  303. return this.songs[prevIndex];
  304. }
  305. /**
  306. * 导航操作:移动到下一首
  307. */
  308. moveToNext(playMode: PlayMode = PlayMode.SEQUENCE): boolean {
  309. if (this.isEmpty()) return false;
  310. let nextIndex = this.currentIndex;
  311. // 添加当前歌曲到播放历史(随机模式)
  312. const currentSong = this.getCurrentSong();
  313. if (currentSong && playMode === PlayMode.RANDOM) {
  314. this.addToPlayHistory(currentSong);
  315. this.playedIndices.add(this.currentIndex);
  316. }
  317. // 直接计算下一个索引,而不依赖getNext方法
  318. switch (playMode) {
  319. case PlayMode.SINGLE_REPEAT:
  320. // 单曲循环,索引不变
  321. LogUtils.getInstance().LOGI(`PlaylistModel: Single repeat mode, staying at index ${this.currentIndex}`);
  322. return true;
  323. case PlayMode.SEQUENCE:
  324. // 顺序播放,循环到开头
  325. nextIndex = (this.currentIndex + 1) % this.songs.length;
  326. break;
  327. case PlayMode.NORMAL:
  328. // 正常播放,不循环
  329. if (this.currentIndex < this.songs.length - 1) {
  330. nextIndex = this.currentIndex + 1;
  331. } else {
  332. LogUtils.getInstance().LOGI('PlaylistModel: Already at last song in normal mode');
  333. return false; // 已经是最后一首
  334. }
  335. break;
  336. case PlayMode.RANDOM:
  337. // 随机播放
  338. nextIndex = this.getRandomNextIndex();
  339. if (nextIndex === -1) {
  340. LogUtils.getInstance().LOGI('PlaylistModel: No available random song');
  341. return false;
  342. }
  343. break;
  344. default:
  345. return false;
  346. }
  347. // 更新索引
  348. if (nextIndex >= 0 && nextIndex < this.songs.length) {
  349. const oldIndex = this.currentIndex;
  350. this.currentIndex = nextIndex;
  351. LogUtils.getInstance().LOGI(`PlaylistModel: Moved to next song from index ${oldIndex} to ${this.currentIndex}`);
  352. return true;
  353. }
  354. return false;
  355. }
  356. /**
  357. * 导航操作:移动到上一首
  358. */
  359. moveToPrevious(playMode: PlayMode = PlayMode.SEQUENCE): boolean {
  360. const prevSong = this.getPrevious(playMode);
  361. if (!prevSong) return false;
  362. // 更新索引
  363. const prevIndex = this.songs.findIndex(song => song.filePath === prevSong.filePath);
  364. if (prevIndex !== -1) {
  365. this.currentIndex = prevIndex;
  366. // 随机播放模式下,从历史记录中移除
  367. if (playMode === PlayMode.RANDOM && this.playHistory.length > 0) {
  368. this.playHistory.pop(); // 移除最后一个(当前播放的)
  369. }
  370. LogUtils.getInstance().LOGI(`PlaylistModel: Moved to previous song at index ${this.currentIndex}`);
  371. return true;
  372. }
  373. return false;
  374. }
  375. /**
  376. * 根据文件路径查找歌曲索引
  377. */
  378. findSongIndex(filePath: string): number {
  379. return this.songs.findIndex(song => song.filePath === filePath);
  380. }
  381. /**
  382. * 播放指定索引的歌曲
  383. */
  384. playSongAtIndex(index: number): boolean {
  385. if (index >= 0 && index < this.songs.length) {
  386. this.currentIndex = index;
  387. LogUtils.getInstance().LOGI(`PlaylistModel: Playing song at index ${index}`);
  388. return true;
  389. }
  390. LogUtils.getInstance().LOGI(`PlaylistModel: Invalid play index ${index}`);
  391. return false;
  392. }
  393. /**
  394. * 获取随机播放的下一个索引
  395. */
  396. private getRandomNextIndex(): number {
  397. const unplayedIndices: number[] = [];
  398. for (let i = 0; i < this.songs.length; i++) {
  399. if (!this.playedIndices.has(i)) {
  400. unplayedIndices.push(i);
  401. }
  402. }
  403. if (unplayedIndices.length === 0) {
  404. // 所有歌曲都播放过了,重置并重新开始
  405. this.playedIndices.clear();
  406. this.playHistory = [];
  407. for (let i = 0; i < this.songs.length; i++) {
  408. if (i !== this.currentIndex) {
  409. unplayedIndices.push(i);
  410. }
  411. }
  412. }
  413. if (unplayedIndices.length === 0) return -1;
  414. const randomIndex = Math.floor(Math.random() * unplayedIndices.length);
  415. return unplayedIndices[randomIndex];
  416. }
  417. /**
  418. * 获取随机播放模式下的上一首
  419. */
  420. private getRandomPrevious(): VideoItem | null {
  421. if (this.playHistory.length >= 2) {
  422. // 返回历史记录的第二首(第一首是当前播放的)
  423. return this.playHistory[this.playHistory.length - 2];
  424. }
  425. return null;
  426. }
  427. /**
  428. * 添加到播放历史记录
  429. */
  430. private addToPlayHistory(song: VideoItem): void {
  431. this.playHistory.push(song);
  432. // 限制历史记录长度,避免内存占用过多
  433. if (this.playHistory.length > 50) {
  434. this.playHistory.shift();
  435. }
  436. }
  437. /**
  438. * 获取播放历史记录
  439. */
  440. getPlayHistory(): VideoItem[] {
  441. const result: VideoItem[] = [];
  442. for (const item of this.playHistory) {
  443. result.push(item);
  444. }
  445. return result;
  446. }
  447. }