about summary refs log tree commit diff
path: root/src/lib/media/video/compress.web.ts
blob: 995fbf1da646fb7b3b7a515922e45fc6ad1142f6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import {ImagePickerAsset} from 'expo-image-picker'

import {VIDEO_MAX_SIZE} from '#/lib/constants'
import {VideoTooLargeError} from '#/lib/media/video/errors'
import {CompressedVideo} from './types'

// doesn't actually compress, converts to ArrayBuffer
export async function compressVideo(
  asset: ImagePickerAsset,
  _opts?: {
    signal?: AbortSignal
    onProgress?: (progress: number) => void
  },
): Promise<CompressedVideo> {
  const {mimeType, base64} = parseDataUrl(asset.uri)
  const blob = base64ToBlob(base64, mimeType)
  const uri = URL.createObjectURL(blob)

  if (blob.size > VIDEO_MAX_SIZE) {
    throw new VideoTooLargeError()
  }

  return {
    size: blob.size,
    uri,
    bytes: await blob.arrayBuffer(),
    mimeType,
  }
}

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})
}