/ app / services / playlist-data-service.js
playlist-data-service.js
  1  import { createEventEmitter, createLocalEventSubscriptionManager } from '../utils/event/index.js';
  2  import { MenuEventData, MenuEvents } from '../services/menu-events.js';
  3  import * as videoSources from '../services/video/sources.js';
  4  import filterSourceList from '../utils/playlist/filter-source-list.js';
  5  
  6  export class PlaylistDataService {
  7    constructor() {
  8      this.events = createEventEmitter();
  9  
 10      this.subscriptions = createLocalEventSubscriptionManager();
 11  
 12      this._filteredListCache = new Map();
 13  
 14      this._currentSearchQuery = '';
 15  
 16      this._operationInProgress = false;
 17    }
 18  
 19    getEventEmitter() {
 20      return this.events;
 21    }
 22  
 23    get currentSearchQuery() {
 24      return this._currentSearchQuery;
 25    }
 26  
 27    set currentSearchQuery(query) {
 28      if (query !== this._currentSearchQuery) {
 29        this._filteredListCache.clear();
 30      }
 31      this._currentSearchQuery = query;
 32    }
 33  
 34    cleanup() {
 35      if (this._unsubscribeMenuCloseRequested) {
 36        this._unsubscribeMenuCloseRequested();
 37        this._unsubscribeMenuCloseRequested = null;
 38      }
 39  
 40      if (this._readyCheckInterval) {
 41        clearInterval(this._readyCheckInterval);
 42        this._readyCheckInterval = null;
 43      }
 44  
 45      if (this._readyCheckTimeout) {
 46        clearTimeout(this._readyCheckTimeout);
 47        this._readyCheckTimeout = null;
 48      }
 49  
 50      const eventData = MenuEventData.createMenuCloseReadyData('right', 'playlist-data');
 51  
 52      this.events.publish(MenuEvents.MENU_CLOSE_READY, eventData);
 53  
 54      this.subscriptions.unsubscribeAll();
 55  
 56      this.events.clearAllEvents();
 57    }
 58  
 59    subscribeToMenuEvents(menuManagerEvents) {
 60      if (menuManagerEvents) {
 61        this.subscriptions.subscribe(
 62          menuManagerEvents,
 63          MenuEvents.MENU_CLOSE_REQUESTED,
 64          data => {
 65            if (data && data.side === 'right') {
 66              this._signalReadyForClose();
 67            }
 68          },
 69          this,
 70        );
 71      } else {
 72        console.warn(
 73          'Menu manager events not available, playlist data service will not respond to menu events',
 74        );
 75      }
 76    }
 77  
 78    _signalReadyForClose() {
 79      if (!this._operationInProgress) {
 80        const eventData = MenuEventData.createMenuCloseReadyData('right', 'playlist-data');
 81  
 82        this.events.publish(MenuEvents.MENU_CLOSE_READY, eventData);
 83      } else {
 84        this._checkAndSignalWhenReady();
 85      }
 86    }
 87  
 88    _checkAndSignalWhenReady() {
 89      if (this._readyCheckInterval) {
 90        clearInterval(this._readyCheckInterval);
 91      }
 92  
 93      if (this._readyCheckTimeout) {
 94        clearTimeout(this._readyCheckTimeout);
 95      }
 96  
 97      this._readyCheckInterval = setInterval(() => {
 98        if (!this._operationInProgress) {
 99          const eventData = MenuEventData.createMenuCloseReadyData('right', 'playlist-data');
100  
101          this.events.publish(MenuEvents.MENU_CLOSE_READY, eventData);
102  
103          clearInterval(this._readyCheckInterval);
104          this._readyCheckInterval = null;
105  
106          if (this._readyCheckTimeout) {
107            clearTimeout(this._readyCheckTimeout);
108            this._readyCheckTimeout = null;
109          }
110        }
111      }, 100);
112  
113      this._readyCheckTimeout = setTimeout(() => {
114        console.warn('Forcing playlist data service ready signal after timeout');
115  
116        if (this._readyCheckInterval) {
117          clearInterval(this._readyCheckInterval);
118          this._readyCheckInterval = null;
119        }
120  
121        const eventData = MenuEventData.createMenuCloseReadyData('right', 'playlist-data');
122  
123        this.events.publish(MenuEvents.MENU_CLOSE_READY, eventData);
124  
125        this._readyCheckTimeout = null;
126      }, 1000);
127    }
128  
129    getSourceList() {
130      return videoSources.getSourceList();
131    }
132  
133    getSourceCount() {
134      return videoSources.getSourceCount();
135    }
136  
137    getVideoInfoByIndex(index) {
138      return videoSources.getVideoInfoByIndex(index);
139    }
140  
141    async shuffleSources() {
142      this._operationInProgress = true;
143  
144      try {
145        this._filteredListCache.clear();
146  
147        videoSources.shuffleSources();
148  
149        this.events.publish(MenuEvents.PLAYLIST_RESHUFFLED);
150      } finally {
151        this._operationInProgress = false;
152      }
153    }
154  
155    async filterSourceList(query = null) {
156      this._operationInProgress = true;
157  
158      try {
159        const searchQuery = query !== null ? query : this._currentSearchQuery;
160  
161        if (!searchQuery) {
162          return this.getSourceList();
163        }
164  
165        if (this._filteredListCache.has(searchQuery)) {
166          return this._filteredListCache.get(searchQuery);
167        }
168  
169        try {
170          const filteredList = filterSourceList(searchQuery, videoSources.getSourceList);
171  
172          this._filteredListCache.set(searchQuery, filteredList);
173  
174          return filteredList;
175        } catch (error) {
176          console.error('Error filtering source list:', error);
177          return [];
178        }
179      } finally {
180        this._operationInProgress = false;
181      }
182    }
183  }
184  
185  export default new PlaylistDataService();