diff --git a/src/lib/media/video/compress.ts b/src/lib/media/video/compress.ts index 95761759..709f2a77 100644 --- a/src/lib/media/video/compress.ts +++ b/src/lib/media/video/compress.ts @@ -1,9 +1,6 @@ import {getVideoMetaData, Video} from 'react-native-compressor' -export type CompressedVideo = { - uri: string - size: number -} +import {CompressedVideo} from './types' export async function compressVideo( file: string, diff --git a/src/lib/media/video/compress.web.ts b/src/lib/media/video/compress.web.ts index 11ccb510..c0870253 100644 --- a/src/lib/media/video/compress.web.ts +++ b/src/lib/media/video/compress.web.ts @@ -1,12 +1,8 @@ import {VideoTooLargeError} from 'lib/media/video/errors' +import {CompressedVideo} from './types' const MAX_VIDEO_SIZE = 1024 * 1024 * 100 // 100MB -export type CompressedVideo = { - uri: string - size: number -} - // doesn't actually compress, but throws if >100MB export async function compressVideo( file: string, @@ -15,8 +11,9 @@ export async function compressVideo( onProgress?: (progress: number) => void }, ): Promise { - const blob = await fetch(file).then(res => res.blob()) - const video = URL.createObjectURL(blob) + const {mimeType, base64} = parseDataUrl(file) + const blob = base64ToBlob(base64, mimeType) + const uri = URL.createObjectURL(blob) if (blob.size > MAX_VIDEO_SIZE) { throw new VideoTooLargeError() @@ -24,6 +21,34 @@ export async function compressVideo( return { size: blob.size, - uri: video, + uri, + bytes: await blob.arrayBuffer(), } } + +function parseDataUrl(dataUrl: string) { + const [mimeType, base64] = dataUrl.slice('data:'.length).split(';base64,') + if (!mimeType || !base64) { + throw new Error('Invalid data URL') + } + return {mimeType, base64} +} + +function base64ToBlob(base64: string, mimeType: string) { + const byteCharacters = atob(base64) + const byteArrays = [] + + for (let offset = 0; offset < byteCharacters.length; offset += 512) { + const slice = byteCharacters.slice(offset, offset + 512) + const byteNumbers = new Array(slice.length) + + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i) + } + + const byteArray = new Uint8Array(byteNumbers) + byteArrays.push(byteArray) + } + + return new Blob(byteArrays, {type: mimeType}) +} diff --git a/src/lib/media/video/types.ts b/src/lib/media/video/types.ts new file mode 100644 index 00000000..ba007005 --- /dev/null +++ b/src/lib/media/video/types.ts @@ -0,0 +1,6 @@ +export type CompressedVideo = { + uri: string + size: number + // web only, can fall back to uri if missing + bytes?: ArrayBuffer +} diff --git a/src/state/queries/video/compress-video.ts b/src/state/queries/video/compress-video.ts index a4c17eac..533b5841 100644 --- a/src/state/queries/video/compress-video.ts +++ b/src/state/queries/video/compress-video.ts @@ -2,7 +2,8 @@ import {ImagePickerAsset} from 'expo-image-picker' import {useMutation} from '@tanstack/react-query' import {cancelable} from '#/lib/async/cancelable' -import {CompressedVideo, compressVideo} from 'lib/media/video/compress' +import {CompressedVideo} from '#/lib/media/video/types' +import {compressVideo} from 'lib/media/video/compress' export function useCompressVideoMutation({ onProgress, diff --git a/src/state/queries/video/video-upload.ts b/src/state/queries/video/video-upload.ts index 5dde5c31..6fdd9d5b 100644 --- a/src/state/queries/video/video-upload.ts +++ b/src/state/queries/video/video-upload.ts @@ -4,7 +4,7 @@ import {useMutation} from '@tanstack/react-query' import {nanoid} from 'nanoid/non-secure' import {cancelable} from '#/lib/async/cancelable' -import {CompressedVideo} from '#/lib/media/video/compress' +import {CompressedVideo} from '#/lib/media/video/types' import {createVideoEndpointUrl} from '#/state/queries/video/util' import {useAgent, useSession} from '#/state/session' import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers' diff --git a/src/state/queries/video/video-upload.web.ts b/src/state/queries/video/video-upload.web.ts index a8494970..c3ad3926 100644 --- a/src/state/queries/video/video-upload.web.ts +++ b/src/state/queries/video/video-upload.web.ts @@ -3,7 +3,7 @@ import {useMutation} from '@tanstack/react-query' import {nanoid} from 'nanoid/non-secure' import {cancelable} from '#/lib/async/cancelable' -import {CompressedVideo} from '#/lib/media/video/compress' +import {CompressedVideo} from '#/lib/media/video/types' import {createVideoEndpointUrl} from '#/state/queries/video/util' import {useAgent, useSession} from '#/state/session' import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers' diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts index f787a6af..3c5094c7 100644 --- a/src/state/queries/video/video.ts +++ b/src/state/queries/video/video.ts @@ -6,8 +6,8 @@ import {useLingui} from '@lingui/react' import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' -import {CompressedVideo} from 'lib/media/video/compress' import {VideoTooLargeError} from 'lib/media/video/errors' +import {CompressedVideo} from 'lib/media/video/types' import {useCompressVideoMutation} from 'state/queries/video/compress-video' import {useVideoAgent} from 'state/queries/video/util' import {useUploadVideoMutation} from 'state/queries/video/video-upload' diff --git a/src/view/com/composer/videos/VideoPreview.tsx b/src/view/com/composer/videos/VideoPreview.tsx index 7e43dcd6..99407627 100644 --- a/src/view/com/composer/videos/VideoPreview.tsx +++ b/src/view/com/composer/videos/VideoPreview.tsx @@ -4,7 +4,7 @@ import {View} from 'react-native' import {ImagePickerAsset} from 'expo-image-picker' import {useVideoPlayer, VideoView} from 'expo-video' -import {CompressedVideo} from '#/lib/media/video/compress' +import {CompressedVideo} from '#/lib/media/video/types' import {ExternalEmbedRemoveBtn} from 'view/com/composer/ExternalEmbedRemoveBtn' import {atoms as a, useTheme} from '#/alf' diff --git a/src/view/com/composer/videos/VideoPreview.web.tsx b/src/view/com/composer/videos/VideoPreview.web.tsx index 5bf4d2a7..8556640b 100644 --- a/src/view/com/composer/videos/VideoPreview.web.tsx +++ b/src/view/com/composer/videos/VideoPreview.web.tsx @@ -2,7 +2,7 @@ import React, {useEffect, useRef} from 'react' import {View} from 'react-native' import {ImagePickerAsset} from 'expo-image-picker' -import {CompressedVideo} from '#/lib/media/video/compress' +import {CompressedVideo} from '#/lib/media/video/types' import {ExternalEmbedRemoveBtn} from 'view/com/composer/ExternalEmbedRemoveBtn' import {atoms as a, useTheme} from '#/alf'