-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Video] Uploads #4754
Merged
Merged
[Video] Uploads #4754
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
bb2840e
state for video uploads
haileyok 9d025cb
get upload working
haileyok d42324c
add a debug log
haileyok 78ebf45
add post progress
haileyok 5445e27
progress
haileyok b06e527
fetch data
haileyok 3a7ca17
add some progress info, web uploads
haileyok eaa05c7
post on finished uploading (wip)
haileyok c663ac2
add a note
haileyok a833cfb
add some todos
haileyok aec7fbf
clear video
haileyok b0e6a63
merge some stuff
haileyok a8622a4
convert to `createUploadTask`
haileyok 988389f
patch expo modules core
haileyok 939a516
working native upload progress
haileyok 5c8de52
platform fork
haileyok 963e6c0
upload progress for web
haileyok 0aaddd5
cleanup
haileyok 0bf011b
cleanup
haileyok 0cfa6b5
more tweaks
haileyok d0d8e87
simplify
haileyok df874fa
fix type errors
mozzius File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* TEMPORARY: THIS IS A TEMPORARY PLACEHOLDER. THAT MEANS IT IS TEMPORARY. I.E. WILL BE REMOVED. NOT TO USE IN PRODUCTION. | ||
* @temporary | ||
* PS: This is a temporary placeholder for the video types. It will be removed once the actual types are implemented. | ||
* Not joking, this is temporary. | ||
*/ | ||
|
||
export interface JobStatus { | ||
jobId: string | ||
did: string | ||
cid: string | ||
state: JobState | ||
progress?: number | ||
errorHuman?: string | ||
errorMachine?: string | ||
} | ||
|
||
export enum JobState { | ||
JOB_STATE_UNSPECIFIED = 'JOB_STATE_UNSPECIFIED', | ||
JOB_STATE_CREATED = 'JOB_STATE_CREATED', | ||
JOB_STATE_ENCODING = 'JOB_STATE_ENCODING', | ||
JOB_STATE_ENCODED = 'JOB_STATE_ENCODED', | ||
JOB_STATE_UPLOADING = 'JOB_STATE_UPLOADING', | ||
JOB_STATE_UPLOADED = 'JOB_STATE_UPLOADED', | ||
JOB_STATE_CDN_PROCESSING = 'JOB_STATE_CDN_PROCESSING', | ||
JOB_STATE_CDN_PROCESSED = 'JOB_STATE_CDN_PROCESSED', | ||
JOB_STATE_FAILED = 'JOB_STATE_FAILED', | ||
JOB_STATE_COMPLETED = 'JOB_STATE_COMPLETED', | ||
} | ||
|
||
export interface UploadVideoResponse { | ||
job_id: string | ||
did: string | ||
cid: string | ||
state: JobState | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import {ImagePickerAsset} from 'expo-image-picker' | ||
import {useMutation} from '@tanstack/react-query' | ||
|
||
import {CompressedVideo, compressVideo} from 'lib/media/video/compress' | ||
|
||
export function useCompressVideoMutation({ | ||
onProgress, | ||
onSuccess, | ||
onError, | ||
}: { | ||
onProgress: (progress: number) => void | ||
onError: (e: any) => void | ||
onSuccess: (video: CompressedVideo) => void | ||
}) { | ||
return useMutation({ | ||
mutationFn: async (asset: ImagePickerAsset) => { | ||
return await compressVideo(asset.uri, { | ||
onProgress: num => onProgress(trunc2dp(num)), | ||
}) | ||
}, | ||
onError, | ||
onSuccess, | ||
onMutate: () => { | ||
onProgress(0) | ||
}, | ||
}) | ||
} | ||
|
||
function trunc2dp(num: number) { | ||
return Math.trunc(num * 100) / 100 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const UPLOAD_ENDPOINT = process.env.EXPO_PUBLIC_VIDEO_ROOT_ENDPOINT ?? '' | ||
|
||
export const createVideoEndpointUrl = ( | ||
route: string, | ||
params?: Record<string, string>, | ||
) => { | ||
const url = new URL(`${UPLOAD_ENDPOINT}`) | ||
url.pathname = route | ||
if (params) { | ||
for (const key in params) { | ||
url.searchParams.set(key, params[key]) | ||
} | ||
} | ||
return url.href | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import {createUploadTask, FileSystemUploadType} from 'expo-file-system' | ||
import {useMutation} from '@tanstack/react-query' | ||
import {nanoid} from 'nanoid/non-secure' | ||
|
||
import {CompressedVideo} from 'lib/media/video/compress' | ||
import {UploadVideoResponse} from 'lib/media/video/types' | ||
import {createVideoEndpointUrl} from 'state/queries/video/util' | ||
import {useSession} from 'state/session' | ||
const UPLOAD_HEADER = process.env.EXPO_PUBLIC_VIDEO_HEADER ?? '' | ||
|
||
export const useUploadVideoMutation = ({ | ||
onSuccess, | ||
onError, | ||
setProgress, | ||
}: { | ||
onSuccess: (response: UploadVideoResponse) => void | ||
onError: (e: any) => void | ||
setProgress: (progress: number) => void | ||
}) => { | ||
const {currentAccount} = useSession() | ||
|
||
return useMutation({ | ||
mutationFn: async (video: CompressedVideo) => { | ||
const uri = createVideoEndpointUrl('/upload', { | ||
did: currentAccount!.did, | ||
name: `${nanoid(12)}.mp4`, // @TODO what are we limiting this to? | ||
}) | ||
|
||
const uploadTask = createUploadTask( | ||
uri, | ||
video.uri, | ||
{ | ||
headers: { | ||
'dev-key': UPLOAD_HEADER, | ||
'content-type': 'video/mp4', // @TODO same question here. does the compression step always output mp4? | ||
}, | ||
httpMethod: 'POST', | ||
uploadType: FileSystemUploadType.BINARY_CONTENT, | ||
}, | ||
p => { | ||
setProgress(p.totalBytesSent / p.totalBytesExpectedToSend) | ||
}, | ||
) | ||
const res = await uploadTask.uploadAsync() | ||
|
||
if (!res?.body) { | ||
throw new Error('No response') | ||
} | ||
|
||
// @TODO rm, useful for debugging/getting video cid | ||
console.log('[VIDEO]', res.body) | ||
const responseBody = JSON.parse(res.body) as UploadVideoResponse | ||
onSuccess(responseBody) | ||
return responseBody | ||
}, | ||
onError, | ||
onSuccess, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import {useMutation} from '@tanstack/react-query' | ||
import {nanoid} from 'nanoid/non-secure' | ||
|
||
import {CompressedVideo} from 'lib/media/video/compress' | ||
import {UploadVideoResponse} from 'lib/media/video/types' | ||
import {createVideoEndpointUrl} from 'state/queries/video/util' | ||
import {useSession} from 'state/session' | ||
const UPLOAD_HEADER = process.env.EXPO_PUBLIC_VIDEO_HEADER ?? '' | ||
|
||
export const useUploadVideoMutation = ({ | ||
onSuccess, | ||
onError, | ||
setProgress, | ||
}: { | ||
onSuccess: (response: UploadVideoResponse) => void | ||
onError: (e: any) => void | ||
setProgress: (progress: number) => void | ||
}) => { | ||
const {currentAccount} = useSession() | ||
|
||
return useMutation({ | ||
mutationFn: async (video: CompressedVideo) => { | ||
const uri = createVideoEndpointUrl('/upload', { | ||
did: currentAccount!.did, | ||
name: `${nanoid(12)}.mp4`, // @TODO what are we limiting this to? | ||
}) | ||
|
||
const bytes = await fetch(video.uri).then(res => res.arrayBuffer()) | ||
|
||
const xhr = new XMLHttpRequest() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using XHR here because |
||
const res = (await new Promise((resolve, reject) => { | ||
xhr.upload.addEventListener('progress', e => { | ||
const progress = e.loaded / e.total | ||
setProgress(progress) | ||
}) | ||
xhr.onloadend = () => { | ||
if (xhr.readyState === 4) { | ||
const uploadRes = JSON.parse( | ||
xhr.responseText, | ||
) as UploadVideoResponse | ||
resolve(uploadRes) | ||
onSuccess(uploadRes) | ||
} else { | ||
reject() | ||
onError(new Error('Failed to upload video')) | ||
} | ||
} | ||
xhr.onerror = () => { | ||
reject() | ||
onError(new Error('Failed to upload video')) | ||
} | ||
xhr.open('POST', uri) | ||
xhr.setRequestHeader('Content-Type', 'video/mp4') // @TODO how we we set the proper content type? | ||
// @TODO remove this header for prod | ||
xhr.setRequestHeader('dev-key', UPLOAD_HEADER) | ||
xhr.send(bytes) | ||
})) as UploadVideoResponse | ||
|
||
// @TODO rm for prod | ||
console.log('[VIDEO]', res) | ||
return res | ||
}, | ||
onError, | ||
onSuccess, | ||
}) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temporary patch for now, see https://bluesky-builders.slack.com/archives/C05R8KJK1PC/p1720682497841229?thread_ts=1720651555.643739&cid=C05R8KJK1PC