Skip to content

Instantly share code, notes, and snippets.

@BlueBazze
Last active November 7, 2023 19:48
Show Gist options
  • Save BlueBazze/4f05a44d5e5712b3fdfa7e50aed2ac8d to your computer and use it in GitHub Desktop.
Save BlueBazze/4f05a44d5e5712b3fdfa7e50aed2ac8d to your computer and use it in GitHub Desktop.
<template>
<v-card>
<v-card-title>{{
$t('pages.mediaSources.components.UploadMediaSourceCard.title')
}}</v-card-title>
<v-card-subtitle></v-card-subtitle>
<v-card-text>
<p>
{{ $t('pages.mediaSources.components.UploadMediaSourceCard.subTitle') }}
</p>
<p>
{{ $t('pages.mediaSources.components.UploadMediaSourceCard.text') }}
</p>
</v-card-text>
<v-form ref="form" v-model="valid" class="mx-4" @submit.prevent="submit">
<v-text-field v-model="form.title" outlined :error="!form.title" :rules="$rules.for($t('title'), ['required'])"
:error-messages="errors.title || errors.name" :label="$t('title')" :disabled="busy" required class="mt-2"
@input="clearErrors" />
<v-file-input autofocus prepend-icon="" outlined type="file" accept="video/mp4" show-size :error="!form.video"
:value="form.video" :rules="$rules.for($t('video'), ['required'])" :error-messages="errors.video"
:label="$t('video')" :loading="uploading" :disabled="busy"
:suffix="upload.progress > 0 ? `${upload.progress}%` : ''" required class="mt-2" @change="setFormVideo"
@input="clearErrors">
</v-file-input>
</v-form>
<!-- Show progress bar for upload progress when uploading -->
<v-progress-linear v-if="uploading" :value="upload.progress" />
<v-card-actions class="justify-end">
<v-btn text color="primary" @click="cancel">
{{ $t('cancel') }}
</v-btn>
<v-btn text color="primary" type="submit" :loading="busy" :disabled="!valid || busy" @click="submit">
{{ $t('upload') }}
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import * as UpChunk from '@mux/upchunk/dist/upchunk'
import { extractErrorsFromLaravelResponse, humanize } from '~/helpers.ts'
export default {
props: {
publisher: Object,
},
data: () => ({
busy: false,
valid: false,
upload: {
progress: 0,
},
uploader: null,
source: undefined,
errors: {},
form: {
title: '',
video: null,
},
videoMetadata: {
video_duration: 0,
height: 0,
width: 0,
},
}),
computed: {
uploading() {
if (!this.upload || !this.upload.progress) {
return false
}
return this.upload.progress > 0 && this.upload.progress < 100
},
},
methods: {
async setFormVideo(file) {
this.form.video = file
if (!this.form.title) {
this.form.title = humanize(
file.name.substring(0, file.name.lastIndexOf('.')) || file.name
)
}
const video = await this.loadVideo(file)
this.videoMetadata.video_duration = Math.round(video.duration)
this.videoMetadata.height = video.videoHeight
this.videoMetadata.width = video.videoWidth
},
cancel() {
if (!this.upload || !this.upload.progress) {
this.upload.abort()
return;
}
this.reset()
this.$emit('cancel')
},
clearErrors() {
this.errors = {}
},
reset() {
this.busy = false
this.valid = false
this.upload.progress = 0
this.form = {
title: '',
video: null,
}
this.videoMetadata = {
video_duration: 0,
height: 0,
width: 0,
}
this.errors = {}
if (this.uploader) {
this.uploader.abort()
this.uploader = null
}
if (this.source) {
this.source = null
}
this.valid = this.$refs.form.validate()
},
loadVideo(file) {
return new Promise((resolve, reject) => {
try {
const video = document.createElement('video')
video.preload = 'metadata'
video.onloadedmetadata = function () {
resolve(this)
}
video.onerror = function () {
reject(new Error('Invalid video. Please select a video file.'))
}
video.src = window.URL.createObjectURL(file)
} catch (e) {
reject(e)
}
})
},
async submit() {
try {
await this.$refs.form.validate()
this.busy = true
if (!this.source) {
this.source = await this.createSource()
}
await this.uploadToMux()
this.$emit('success', this.source)
this.$api.$patch(`/broadcast-sources/${this.source.id}`, {
uploaded_at: new Date(),
})
this.reset()
} catch (error) {
this.$emit('error', error)
this.errors = error
} finally {
this.busy = false
}
},
createSource() {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
await this.$refs.form.validate()
const source = await this.$api.$post(
`publishers/${this.publisher.id}/broadcast-sources`,
{
name: this.form.title,
filename: this.form.video.name,
mimetype: this.form.video.type,
video_width: this.videoMetadata.width,
video_height: this.videoMetadata.height,
video_duration: this.videoMetadata.video_duration,
}
)
resolve(source)
} catch (error) {
if (error.response?.status === 422) {
const validationErrors = {
title: [],
video: [],
}
Object.entries(
extractErrorsFromLaravelResponse(error.response)
).forEach(([key, message]) => {
if (key === 'name') {
validationErrors.title.push(...message)
} else {
validationErrors.video.push(...message)
}
})
reject(validationErrors)
} else {
reject(error)
}
}
})
},
uploadToMux() {
return new Promise((resolve, reject) => {
this.uploader = UpChunk.createUpload({
endpoint: this.source.upload_url,
file: this.form.video,
chunkSize: 5120, // Uploads the file in ~5mb chunks
})
// subscribe to events
/**
type EventName =
| 'attempt'
| 'attemptFailure'
| 'chunkSuccess'
| 'error'
| 'offline'
| 'online'
| 'progress'
| 'success';
*/
this.uploader.on('error', (error) => {
console.error(error)
this.$rollbar.error(error)
reject(error)
})
this.uploader.on('progress', (progress) => {
console.log(JSON.stringify(progress))
this.upload.progress = progress
})
this.uploader.on('success', () => {
resolve()
})
})
},
},
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment