diff --git a/frontend/src/api/document.ts b/frontend/src/api/document.ts index ae86e5c8..5f59846d 100644 --- a/frontend/src/api/document.ts +++ b/frontend/src/api/document.ts @@ -1,4 +1,4 @@ -import { fetcher, makeSwrHook } from '../api'; +import { RequestDataType, fetcher, makeSwrHook } from '../api'; export const listDocuments = fetcher.path('/api/v1/documents/').method('get').create(); export const createDocument = fetcher @@ -19,7 +19,7 @@ export const useListDocuments = makeSwrHook('listDocuments', listDocuments); export const useGetDocument = makeSwrHook('getDocument', getDocument); export const useGetDocumentTasks = makeSwrHook('getDocumentTasks', getDocumentTasks); -export type ApiDocument = ReturnType['data']; +export type ApiDocument = RequestDataType; export const deleteDocument = fetcher .path('/api/v1/documents/{document_id}/') diff --git a/frontend/src/editor/export/index.tsx b/frontend/src/editor/export/index.tsx index b8241b84..c7e330fe 100644 --- a/frontend/src/editor/export/index.tsx +++ b/frontend/src/editor/export/index.tsx @@ -12,7 +12,7 @@ export type ExportProps = { outputNameBase: string; editor: Editor; onClose: () => void; - document: ApiDocument; + document?: ApiDocument; }; export type ExportType = { @@ -48,7 +48,7 @@ export function ExportModal({ }: { onClose: () => void; editor: Editor; - document: ApiDocument; + document?: ApiDocument; } & Omit, 'label'>) { const [exportType, setExportType] = useState(exportTypes[0]); const ExportBodyComponent = exportType.component; diff --git a/frontend/src/editor/export/transcribee.tsx b/frontend/src/editor/export/transcribee.tsx index bf9a6461..27b5de06 100644 --- a/frontend/src/editor/export/transcribee.tsx +++ b/frontend/src/editor/export/transcribee.tsx @@ -5,23 +5,19 @@ import { Checkbox } from '../../components/form'; import { downloadBinaryAsFile } from '../../utils/download_text_as_file'; import { ExportProps } from '.'; import { HttpReader, Uint8ArrayReader, ZipWriter, Uint8ArrayWriter } from '@zip.js/zip.js'; -import { sortMediaFiles } from '../../utils/use_audio'; import { LoadingSpinnerButton, SecondaryButton } from '../../components/button'; +import { splitAndSortMediaFiles } from '../player'; export function TranscribeeExportBody({ onClose, outputNameBase, editor, document }: ExportProps) { const [loading, setLoading] = useState(false); const [includeOriginalMediaFile, setIncludeOriginalMediaFile] = useState(false); const bestMediaUrl = useMemo(() => { - const mappedFiles = - document?.media_files.map((media) => { - return { - src: media.url, - type: media.content_type, - }; - }) || []; - - return sortMediaFiles(mappedFiles)[0].src; + const { videoSources, audioSources, hasVideo } = splitAndSortMediaFiles( + document?.media_files || [], + ); + const bestSource = hasVideo ? videoSources[0] : audioSources[0]; + return bestSource.src; }, [document?.media_files]); const originalMediaUrl = useMemo(() => { diff --git a/frontend/src/editor/player.tsx b/frontend/src/editor/player.tsx index 44413e53..8200122d 100644 --- a/frontend/src/editor/player.tsx +++ b/frontend/src/editor/player.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import { IconButton } from '../components/button'; import { ImBackward2, ImPause2, ImPlay3 } from 'react-icons/im'; import { useCallback, useMemo, useEffect, useState, useRef, useContext } from 'react'; -import { useGetDocument } from '../api/document'; +import { ApiDocument, useGetDocument } from '../api/document'; import { CssRule } from '../utils/cssdom'; import { SEEK_TO_EVENT, SeekToEvent } from './types'; import { useEvent } from '../utils/use_event'; @@ -24,6 +24,28 @@ const SKIP_SHORTCUT_SEC = 3; let lastTabPressTs = 0; +export function splitAndSortMediaFiles(mediaFiles: ApiDocument['media_files']) { + const videoFiles = mediaFiles.filter((media) => media.tags.includes('video')); + const audioFiles = mediaFiles.filter((media) => !media.tags.includes('video')); + + const mapFile = (media: (typeof mediaFiles)[0]) => { + return { + src: media.url, + type: media.content_type, + tags: media.tags, + }; + }; + + const mappedVideoFiles = videoFiles.map(mapFile); + const mappedAudioFiles = audioFiles.map(mapFile); + + return { + videoSources: sortMediaFiles(mappedVideoFiles), + audioSources: sortMediaFiles(mappedAudioFiles), + hasVideo: videoFiles.length > 0, + }; +} + export function PlayerBar({ documentId, editor, @@ -41,43 +63,15 @@ export function PlayerBar({ }, ); - const [playbackRate, setPlaybackRate] = useLocalStorage('playbackRate', 1); - - const { videoSources, audioSources, hasVideo } = useMemo(() => { - // do not play the original file, it may be large - let relevantMediaFiles = - data?.media_files.filter((media) => !media.tags.includes('original')) || []; - - // but if the original is all we have, better play this than nothing at all - if ( - relevantMediaFiles.length == 0 && - data?.media_files !== undefined && - data?.media_files.length > 0 - ) { - relevantMediaFiles = data.media_files; - } - - const videoFiles = relevantMediaFiles.filter((media) => media.tags.includes('video')); - const audioFiles = relevantMediaFiles.filter((media) => !media.tags.includes('video')); - - const mapFile = (media: (typeof relevantMediaFiles)[0]) => { - return { - src: media.url, - type: media.content_type, - }; - }; - - const mappedVideoFiles = videoFiles.map(mapFile); - const mappedAudioFiles = audioFiles.map(mapFile); + const { videoSources, audioSources, hasVideo } = useMemo( + () => splitAndSortMediaFiles(data?.media_files || []), + [data?.media_files], + ); - return { - videoSources: sortMediaFiles(mappedVideoFiles), - audioSources: sortMediaFiles(mappedAudioFiles), - hasVideo: videoFiles.length > 0, - }; - }, [data?.media_files]); + const [playbackRate, setPlaybackRate] = useLocalStorage('playbackRate', 1); - const [showVideo, setShowVideo] = useState(hasVideo); + const [_showVideo, setShowVideo] = useState(true); + const showVideo = _showVideo && hasVideo; const audio = useAudio({ playbackRate, diff --git a/frontend/src/editor/transcription_editor.tsx b/frontend/src/editor/transcription_editor.tsx index c5becb43..e1096e6a 100644 --- a/frontend/src/editor/transcription_editor.tsx +++ b/frontend/src/editor/transcription_editor.tsx @@ -314,7 +314,9 @@ export function TranscriptionEditor({ className={clsx('2xl:-ml-20')} /> - + {!loadingState[0] && ( + + )} diff --git a/frontend/src/utils/use_audio.ts b/frontend/src/utils/use_audio.ts index 67410447..0d7b026d 100644 --- a/frontend/src/utils/use_audio.ts +++ b/frontend/src/utils/use_audio.ts @@ -158,16 +158,21 @@ const MEDIA_PRIORITY = [ 'audio/mpeg', ]; -export function sortMediaFiles(mediaFiles: T[]) { +export function sortMediaFiles(mediaFiles: T[]) { const sorted = []; + const relevantMediaFiles = mediaFiles.filter((media) => !media.tags.includes('original')); + const originalMediaFiles = mediaFiles.filter((media) => media.tags.includes('original')); + for (const contentType of MEDIA_PRIORITY) { - const files = mediaFiles.filter((file) => file.type == contentType); + const files = relevantMediaFiles.filter((file) => file.type == contentType); sorted.push(...files); } - const rest = mediaFiles.filter((file) => !MEDIA_PRIORITY.includes(file.type)); + const rest = relevantMediaFiles.filter((file) => !MEDIA_PRIORITY.includes(file.type)); sorted.push(...rest); + sorted.push(...originalMediaFiles); + return sorted; }