about summary refs log tree commit diff
path: root/src/view/com/composer/videos/pickVideo.web.ts
blob: c358727ef78d0f4e7f6992f7856c4294ee01e9c0 (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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import {type ImagePickerAsset, type ImagePickerResult} from 'expo-image-picker'

import {SUPPORTED_MIME_TYPES} from '#/lib/constants'

// mostly copied from expo-image-picker and adapted to support gifs
// also adds support for reading video metadata

export async function pickVideo(): Promise<ImagePickerResult> {
  const input = document.createElement('input')
  input.style.display = 'none'
  input.setAttribute('type', 'file')
  // TODO: do we need video/* here? -sfn
  input.setAttribute('accept', SUPPORTED_MIME_TYPES.join(','))
  input.setAttribute('id', String(Math.random()))

  document.body.appendChild(input)

  return new Promise(resolve => {
    input.addEventListener('change', async () => {
      if (input.files) {
        const file = input.files[0]
        resolve({
          canceled: false,
          assets: [await getVideoMetadata(file)],
        })
      } else {
        resolve({canceled: true, assets: null})
      }
      document.body.removeChild(input)
    })

    const event = new MouseEvent('click')
    input.dispatchEvent(event)
  })
}

// TODO: we're converting to a dataUrl here, and then converting back to an
// ArrayBuffer in the compressVideo function. This is a bit wasteful, but it
// lets us use the ImagePickerAsset type, which the rest of the code expects.
// We should unwind this and just pass the ArrayBuffer/objectUrl through the system
// instead of a string -sfn
export const getVideoMetadata = (file: File): Promise<ImagePickerAsset> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => {
      const uri = reader.result as string

      if (file.type === 'image/gif') {
        const img = new Image()
        img.onload = () => {
          resolve({
            uri,
            mimeType: 'image/gif',
            width: img.width,
            height: img.height,
            // todo: calculate gif duration. seems possible if you read the bytes
            // https://codepen.io/Ryman/pen/nZpYwY
            // for now let's just let the server reject it, since that seems uncommon -sfn
            duration: null,
          })
        }
        img.onerror = (_ev, _source, _lineno, _colno, error) => {
          console.log('Failed to grab GIF metadata', error)
          reject(new Error('Failed to grab GIF metadata'))
        }
        img.src = uri
      } else {
        const video = document.createElement('video')
        const blobUrl = URL.createObjectURL(file)

        video.preload = 'metadata'
        video.src = blobUrl

        video.onloadedmetadata = () => {
          URL.revokeObjectURL(blobUrl)
          resolve({
            uri,
            mimeType: file.type,
            width: video.videoWidth,
            height: video.videoHeight,
            // convert seconds to ms
            duration: video.duration * 1000,
          })
        }
        video.onerror = (_ev, _source, _lineno, _colno, error) => {
          URL.revokeObjectURL(blobUrl)
          console.log('Failed to grab video metadata', error)
          reject(new Error('Failed to grab video metadata'))
        }
      }
    }
    reader.readAsDataURL(file)
  })
}