diff --git a/src/renderer/components/PlayingView/SubtitleControl.vue b/src/renderer/components/PlayingView/SubtitleControl.vue index e75c482f1b..8e7138bd56 100644 --- a/src/renderer/components/PlayingView/SubtitleControl.vue +++ b/src/renderer/components/PlayingView/SubtitleControl.vue @@ -210,9 +210,6 @@ export default { enabledSecondarySub(val: boolean) { if (!val) this.updateSubtitleType(true); }, - computedAvailableItems(val: SubtitleControlListItem[]) { - this.updateNoSubtitle(!val.length); - }, list(val: SubtitleControlListItem[]) { this.computedAvailableItems = val.map((sub: SubtitleControlListItem) => ({ ...sub, @@ -348,7 +345,6 @@ export default { changeSecondarySubtitle: smActions.changeSecondarySubtitle, refreshSubtitles: smActions.refreshSubtitles, deleteCurrentSubtitle: smActions.deleteSubtitlesByUuid, - updateNoSubtitle: subtitleActions.UPDATE_NO_SUBTITLE, updateSubtitleType: subtitleActions.UPDATE_SUBTITLE_TYPE, }), offCurrentSubtitle() { diff --git a/src/renderer/services/subtitle/searchers.ts b/src/renderer/services/subtitle/searchers.ts index 8701e90bba..ca70f9471a 100644 --- a/src/renderer/services/subtitle/searchers.ts +++ b/src/renderer/services/subtitle/searchers.ts @@ -49,7 +49,7 @@ export async function fetchOnlineList( return Sagi.mediaTranslate({ mediaIdentity, languageCode, hints: hints || basename(videoSrc, extname(videoSrc)), format: '', startTime: 0, // tempoary useless params according to server-side - }); + }).catch(() => []); } export function retrieveEmbeddedList( diff --git a/src/renderer/store/actionTypes.js b/src/renderer/store/actionTypes.js index 7f965a4918..2638707cdc 100644 --- a/src/renderer/store/actionTypes.js +++ b/src/renderer/store/actionTypes.js @@ -41,7 +41,6 @@ export const Subtitle = { UPDATE_SUBTITLE_STYLE: 'UPDATE_SUBTITLE_STYLE', UPDATE_SUBTITLE_SIZE: 'UPDATE_SUBTITLE_SIZE', UPDATE_LAST_SUBTITLE_SIZE: 'UPDATE_LAST_SUBTITLE_SIZE', - UPDATE_NO_SUBTITLE: 'UPDATE_NO_SUBTITLE', UPDATE_SUBTITLE_TOP: 'UPDATE_SUBTITLE_TOP', REMOVE_LOCAL_SUBTITLE: 'REMOVE_LOCAL_SUBTITLE', UPDATE_SUBTITLE_TYPE: 'UPDATE_SUBTITLE_TYPE', @@ -74,6 +73,7 @@ export const SubtitleManager = { removeSubtitle: 'REMOVE_SUBTITLE', refreshSubtitlesInitially: 'REFRESH_SUBTITLES_INITIALLY', refreshSubtitles: 'REFRESH_SUBTITLES', + refreshOnlineSubtitles: 'REFRESH_ONLINE_SUBTITLES', addLocalSubtitles: 'ADD_LOCAL_SUBTITLES', addLocalSubtitlesWithSelect: 'ADD_LOCAL_SUBTITLES_WITH_SELECT', addEmbeddedSubtitles: 'ADD_EMBEDDED_SUBTITLES', diff --git a/src/renderer/store/modules/Subtitle/index.js b/src/renderer/store/modules/Subtitle/index.js index b6b451b183..54f20d4c6e 100644 --- a/src/renderer/store/modules/Subtitle/index.js +++ b/src/renderer/store/modules/Subtitle/index.js @@ -25,7 +25,6 @@ const state = { lastChosenSize: 1, subtitleDelay: 0, scaleNum: 1, - calculatedNoSub: true, subToTop: false, isFirstSubtitle: true, isPrimarySubSettings: true, @@ -76,7 +75,6 @@ const getters = { chosenSize: state => state.chosenSize, lastChosenSize: state => state.lastChosenSize, scaleNum: state => state.scaleNum, - calculatedNoSub: state => state.calculatedNoSub, subToTop: state => state.subToTop, isFirstSubtitle: state => state.isFirstSubtitle, enabledSecondarySub: state => state.enabledSecondarySub, @@ -150,9 +148,6 @@ const mutations = { [subtitleMutations.SUBTITLE_SIZE_UPDATE](state, payload) { state.chosenSize = payload; }, - [subtitleMutations.NO_SUBTITLE_UPDATE](state, payload) { - state.calculatedNoSub = payload; - }, [subtitleMutations.SUBTITLE_TOP_UPDATE](state, payload) { state.subToTop = payload; }, @@ -290,9 +285,6 @@ const actions = { [subtitleActions.UPDATE_SUBTITLE_SIZE]({ commit }, delta) { commit(subtitleMutations.SUBTITLE_SIZE_UPDATE, delta); }, - [subtitleActions.UPDATE_NO_SUBTITLE]({ commit }, delta) { - commit(subtitleMutations.NO_SUBTITLE_UPDATE, delta); - }, [subtitleActions.UPDATE_SUBTITLE_TOP]({ commit }, delta) { commit(subtitleMutations.SUBTITLE_TOP_UPDATE, delta); }, diff --git a/src/renderer/store/modules/SubtitleManager.ts b/src/renderer/store/modules/SubtitleManager.ts index a0529e6892..b45fc0946e 100644 --- a/src/renderer/store/modules/SubtitleManager.ts +++ b/src/renderer/store/modules/SubtitleManager.ts @@ -70,6 +70,7 @@ const getters = { }, primaryDelay({ primaryDelay }: SubtitleManagerState) { return primaryDelay; }, secondaryDelay({ secondaryDelay }: SubtitleManagerState) { return secondaryDelay; }, + calculatedNoSub(state: any, { list }: any) { return !list.length; }, }; const mutations = { [m.setPlaylistId](state: SubtitleManagerState, id: number) { @@ -123,7 +124,7 @@ type AddSubtitleOptions = { playlistId: number; mediaItemId: string; }; -function privacyConfirm() { +function privacyConfirm(): Promise { const { $bus } = Vue.prototype; $bus.$emit('privacy-confirm'); return new Promise((resolve) => { @@ -138,7 +139,7 @@ function setDelayTimeout() { clearTimeout(alterDelayTimeoutId); alterDelayTimeoutId = setTimeout(() => store.dispatch(a.storeSubtitleDelays), 10000); } -function fetchOnlineListWithErrorHandling( +function fetchOnlineListWithBubble( videoSrc: string, languageCode: LanguageCode, hints?: string, @@ -186,109 +187,143 @@ const actions = { secondarySelectionComplete = false; commit(m.setIsRefreshing, true); dispatch(a.startAISelection); + const { playlistId, mediaItemId } = state; + const { + primaryLanguage, secondaryLanguage, + originSrc, + privacyAgreement, + } = getters; const preference = await retrieveSubtitlePreference(playlistId, mediaItemId); - const { primaryLanguage, secondaryLanguage } = getters; - const needRefreshing = ( + const hasStoredSubtitles = !!preference && !!preference.list.length; + const languageHasChanged = ( !preference || - !preference.list.length || !!differenceWith( Object.values(preference.language), [primaryLanguage, secondaryLanguage], ).length ); - if (preference && !needRefreshing) { - try { - await Promise.race([ - dispatch(a.addDatabaseSubtitles, { - storedList: preference.list, - selected: preference.selected, - playlistId, mediaItemId, - }), - new Promise((resolve, reject) => setTimeout(() => reject('timeout'), 10000)), - ]); - } catch(error) { - console.error(error); - } finally { + + if (hasStoredSubtitles && !languageHasChanged && preference) { + return Promise.race([ + dispatch(a.addDatabaseSubtitles, { + storedList: preference.list, + selected: preference.selected, + playlistId, mediaItemId, + }), + new Promise((resolve, reject) => setTimeout(() => reject('timeout'), 10000)), + ]) + .catch(console.error) + .finally(() => { + commit(m.setIsRefreshing, false); + dispatch(legacyActions.UPDATE_SUBTITLE_TYPE, true); + dispatch(a.stopAISelection); + }); + } + + if (hasStoredSubtitles && preference) await dispatch(a.addDatabaseSubtitles, { + storedList: preference.list, + selected: preference.selected, + playlistId, mediaItemId, + }); + + let onlinePromise = Promise.resolve(); + /** whether or not to refresh online subtitles */ + let onlineNeeded = (languageHasChanged || !hasStoredSubtitles) && ['mkv', 'avi', 'ts', 'mp4'].includes(extname(originSrc).slice(1)) && privacyAgreement; + if (onlineNeeded) onlinePromise = dispatch(a.refreshOnlineSubtitles); + /** whether or not to refresh embedded subtitles */ + const embeddedNeeded = getters.list.some(({ type }: SubtitleControlListItem) => type === Type.Embedded); + if (embeddedNeeded) retrieveEmbeddedList(originSrc).then((streams) => dispatch(a.addEmbeddedSubtitles, streams)); + + return Promise.race([ + Promise.all([ + onlinePromise, + dispatch(a.addLocalSubtitles, await searchForLocalList(originSrc)), + ]), + new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeout')), 10000)), + ]) + .catch(console.error) + .finally(() => { + dispatch(a.stopAISelection); + storeSubtitleLanguage([primaryLanguage, secondaryLanguage], playlistId, mediaItemId); + addSubtitleItemsToList(getters.list, playlistId, mediaItemId); + dispatch(a.checkLocalSubtitles); + dispatch(a.checkSubtitleList); commit(m.setIsRefreshing, false); dispatch(legacyActions.UPDATE_SUBTITLE_TYPE, true); - dispatch(a.stopAISelection); - } - } else if (!preference && needRefreshing) { - return dispatch(a.refreshSubtitles, { playlistId, mediaItemId, noOnline: !getters.privacyAgreement }); - } else if (preference && needRefreshing) { - return dispatch(a.addDatabaseSubtitles, { - storedList: preference.list, - selected: preference.selected, - playlistId, mediaItemId, - }) - .then(() => dispatch(a.refreshSubtitles, { playlistId, mediaItemId, noOnline: !getters.privacyAgreement })); - } + }); }, - async [a.refreshSubtitles]( - { state, getters, dispatch, commit }: any, - args: { playlistId: number, mediaItemId: string, noOnline: boolean }, - ) { - const { playlistId, mediaItemId } = args || state; + async [a.refreshSubtitles]({ state, getters, dispatch, commit }: any) { + const { playlistId, mediaItemId } = state; + const { + originSrc, + primaryLanguage, secondaryLanguage, + privacyAgreement, + } = getters; primarySelectionComplete = false; secondarySelectionComplete = false; commit(m.setIsRefreshing, true); dispatch(a.startAISelection); - const { list } = getters as { list: SubtitleControlListItem[] }; - const { originSrc, primaryLanguage, secondaryLanguage } = getters; - let onlinePromise = new Promise((resolve) => resolve()); - /** do not serach online subtitles if extension is not one of mkv, ts, avi and mp4 */ - let online = args && args.noOnline ? false : ['mkv', 'avi', 'ts', 'mp4'].includes(extname(originSrc).slice(1)); - if (online && !getters.privacyAgreement) online = !!(await privacyConfirm()); - if (online) { - addBubble(ONLINE_LOADING); - const hints = generateHints(originSrc); - onlinePromise = Promise.all([ - fetchOnlineListWithErrorHandling(originSrc, primaryLanguage, hints), - fetchOnlineListWithErrorHandling(originSrc, secondaryLanguage, hints), - ]).then((resultsList) => { - const results = flatten(resultsList); - const newSubtitlesToAdd: TranscriptInfo[] = []; - const oldSubtitlesToDel: SubtitleControlListItem[] = []; - const oldSubtitles = [...(getters as { list: SubtitleControlListItem[] }).list]; - oldSubtitlesToDel.push(...remove(oldSubtitles, ({ type, language }) => type === Type.Online && language !== primaryLanguage && language !== secondaryLanguage)); - oldSubtitlesToDel.push(...remove(oldSubtitles, ({ type, hash }) => type === Type.Online && !results.find(({ transcriptIdentity }) => transcriptIdentity === hash))); - newSubtitlesToAdd.push(...results.filter(({ transcriptIdentity }) => !oldSubtitles.find(({ id }) => id === transcriptIdentity))); - return { delete: oldSubtitlesToDel, add: newSubtitlesToAdd }; - }).then((result) => dispatch(a.addOnlineSubtitles, { transcriptInfoList: result.add, playlistId, mediaItemId }) - .then(() => dispatch(a.deleteSubtitlesByUuid, result.delete))); - } - /** whether to search embedded subtitles */ - const embedded = list.some(({ type }) => type === Type.Embedded); - if (embedded) retrieveEmbeddedList(originSrc).then((streams) => dispatch(a.addEmbeddedSubtitles, { streams, playlistId, mediaItemId })); - try { - await Promise.race([ - Promise.all([ - onlinePromise, - dispatch(a.addLocalSubtitles, { paths: await searchForLocalList(originSrc), playlistId, mediaItemId }), - ]), - new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeout')), 10000)), - ]); - dispatch(a.stopAISelection); - storeSubtitleLanguage([primaryLanguage, secondaryLanguage], playlistId, mediaItemId); - addSubtitleItemsToList(getters.list, playlistId, mediaItemId); - dispatch(a.checkLocalSubtitles); - } catch(ex) { - console.error(ex); - } finally { - dispatch(a.checkSubtitleList); - commit(m.setIsRefreshing, false); - dispatch(legacyActions.UPDATE_SUBTITLE_TYPE, true); - } + const onlineNeeded = privacyAgreement ? true : await privacyConfirm(); + const onlinePromise = onlineNeeded ? dispatch(a.refreshOnlineSubtitles, true) : Promise.resolve(); + return Promise.race([ + Promise.all([ + onlinePromise, + dispatch(a.addLocalSubtitles, await searchForLocalList(originSrc)), + ]), + new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeout')), 10000)), + ]) + .catch(console.error) + .finally(() => { + dispatch(a.stopAISelection); + storeSubtitleLanguage([primaryLanguage, secondaryLanguage], playlistId, mediaItemId); + addSubtitleItemsToList(getters.list, playlistId, mediaItemId); + dispatch(a.checkLocalSubtitles); + dispatch(a.checkSubtitleList); + commit(m.setIsRefreshing, false); + dispatch(legacyActions.UPDATE_SUBTITLE_TYPE, true); + }); + }, + async [a.refreshOnlineSubtitles]({ getters, dispatch }: any, bubble?: boolean) { + const { + originSrc, + primaryLanguage, secondaryLanguage, + } = getters; + if (bubble) addBubble(ONLINE_LOADING); + const hints = generateHints(originSrc); + return Promise.all(bubble ? [ + fetchOnlineListWithBubble(originSrc, primaryLanguage, hints), + fetchOnlineListWithBubble(originSrc, secondaryLanguage, hints), + ] : [ + fetchOnlineList(originSrc, primaryLanguage, hints), + fetchOnlineList(originSrc, secondaryLanguage, hints), + ]).then((resultsList) => { + const results = flatten(resultsList); + const newSubtitlesToAdd: TranscriptInfo[] = []; + const oldSubtitlesToDel: SubtitleControlListItem[] = []; + const oldSubtitles = [...(getters as { list: SubtitleControlListItem[] }).list]; + // delete subtitles not matching the current language preference + oldSubtitlesToDel.push(...remove(oldSubtitles, ({ type, language }) => type === Type.Online && language !== primaryLanguage && language !== secondaryLanguage)); + // delete subtitles not existed in the new subtitles + oldSubtitlesToDel.push(...remove(oldSubtitles, ({ type, hash }) => type === Type.Online && !results.find(({ transcriptIdentity }) => transcriptIdentity === hash))); + // add subtitles not existed in the old subtitles + newSubtitlesToAdd.push(...results.filter(({ transcriptIdentity }) => !oldSubtitles.find(({ id }) => id === transcriptIdentity))); + return { delete: oldSubtitlesToDel, add: newSubtitlesToAdd }; + }).then((result) => dispatch(a.addOnlineSubtitles, result.add) + .then(() => dispatch(a.deleteSubtitlesByUuid, result.delete))); }, [a.checkLocalSubtitles]({ dispatch, getters }: any) { const localInvalidSubtitles = getters.list.filter(({ type, source }: any) => type === Type.Local && !existsSync(source)); if (localInvalidSubtitles.length) return dispatch(a.deleteSubtitlesByUuid, localInvalidSubtitles).then(() => addBubble(LOCAL_SUBTITLE_REMOVED)); }, - async [a.addLocalSubtitles]({ dispatch }: any, { paths, playlistId, mediaItemId }: any) { + async [a.addLocalSubtitles]({ dispatch, state }: any, paths: string[]) { return Promise.all( - paths.map((path: string) => dispatch(a.addSubtitle, { generator: new LocalGenerator(path), playlistId, mediaItemId })) + paths.map((path: string) => dispatch(a.addSubtitle, { + generator: new LocalGenerator(path), + playlistId: state.playlistId, + mediaItemId: state.mediaItemId, + })) ); }, async [a.addLocalSubtitlesWithSelect]({ state, dispatch, getters }: any, paths: string[]) { @@ -320,14 +355,22 @@ const actions = { }); } }, - async [a.addEmbeddedSubtitles]({ dispatch }: any, { streams, playlistId, mediaItemId }: any) { + async [a.addEmbeddedSubtitles]({ dispatch, state }: any, streams: [string, ISubtitleStream][]) { return Promise.all( - streams.map((stream: [string, ISubtitleStream]) => dispatch(a.addSubtitle, { generator: new EmbeddedGenerator(stream[0], stream[1]), playlistId, mediaItemId })) + streams.map((stream) => dispatch(a.addSubtitle, { + generator: new EmbeddedGenerator(stream[0], stream[1]), + playlistId: state.playlistId, + mediaItemId: state.mediaItemId, + })) ); }, - async [a.addOnlineSubtitles]({ dispatch }: any, { transcriptInfoList, playlistId, mediaItemId }: any) { + async [a.addOnlineSubtitles]({ dispatch, state }: any, transcriptInfoList: TranscriptInfo[]) { return Promise.all( - transcriptInfoList.map((info: TranscriptInfo) => dispatch(a.addSubtitle, { generator: new OnlineGenerator(info), playlistId, mediaItemId })) + transcriptInfoList.map((info: TranscriptInfo) => dispatch(a.addSubtitle, { + generator: new OnlineGenerator(info), + playlistId: state.playlistId, + mediaItemId: state.mediaItemId, + })) ); }, async [a.addDatabaseSubtitles]({ getters, dispatch }: any, options: AddDatabaseSubtitlesOptions) { diff --git a/src/renderer/store/mutationTypes.js b/src/renderer/store/mutationTypes.js index 0a5dd8a1f7..f3fe6b8e86 100644 --- a/src/renderer/store/mutationTypes.js +++ b/src/renderer/store/mutationTypes.js @@ -64,7 +64,6 @@ export const Subtitle = { SUBTITLE_STYLE_UPDATE: 'SUBTITLE_STYLE_UPDATE', SUBTITLE_SIZE_UPDATE: 'SUBTITLE_SIZE_UPDATE', LAST_SUBTITLE_SIZE_UPDATE: 'LAST_SUBTITLE_SIZE_UPDATE', - NO_SUBTITLE_UPDATE: 'NO_SUBTITLE_UPDATE', SUBTITLE_TOP_UPDATE: 'SUBTITLE_TOP_UPDATE', CURRENT_SUBTITLE_REMOVE: 'CURRENT_SUBTITLE_REMOVE', SUBTITLE_TYPE_UPDATE: 'SUBTITLE_TYPE_UPDATE',