add ability to save media objects to canvas-media
fixes COMMS-2314 refs COMMS-2293 Test Plan - create a media type assignment - as a student in a2 with notorious running locally go to record a video/upload a video file - notice it works and posts back to the containing dom Change-Id: I6c00c688b33deee017b7babb3729ff57d3dd2114 Reviewed-on: https://gerrit.instructure.com/205605 Tested-by: Jenkins Reviewed-by: Landon Gilbert-Bland <lbland@instructure.com> QA-Review: Steven Burnett <sburnett@instructure.com> Product-Review: Steven Burnett <sburnett@instructure.com>
This commit is contained in:
parent
c8261d1476
commit
e614f14517
|
@ -21,36 +21,57 @@ import Button from '@instructure/ui-buttons/lib/components/Button'
|
|||
import closedCaptionLanguages from '../../../../shared/closedCaptionLanguages'
|
||||
import I18n from 'i18n!assignments_2_text_entry'
|
||||
import {IconAttachMediaLine} from '@instructure/ui-icons'
|
||||
import React, {useState} from 'react'
|
||||
import React from 'react'
|
||||
import UploadMedia from '@instructure/canvas-media'
|
||||
import {UploadMediaStrings, MediaCaptureStrings} from '../../../../shared/UploadMediaTranslations'
|
||||
import View from '@instructure/ui-layout/lib/components/View'
|
||||
|
||||
export default function MediaAttempt() {
|
||||
const [mediaModalOpen, setMediaModalOpen] = useState(false)
|
||||
const languages = Object.keys(closedCaptionLanguages).map(key => {
|
||||
return {id: key, label: closedCaptionLanguages[key]}
|
||||
})
|
||||
|
||||
const languages = Object.keys(closedCaptionLanguages).map(key => {
|
||||
return {id: key, label: closedCaptionLanguages[key]}
|
||||
})
|
||||
export default class MediaAttempt extends React.Component {
|
||||
state = {
|
||||
mediaModalOpen: false,
|
||||
mediaObjectUrl: null
|
||||
}
|
||||
|
||||
return (
|
||||
<View as="div" borderWidth="small">
|
||||
<UploadMedia
|
||||
uploadMediaTranslations={{UploadMediaStrings, MediaCaptureStrings}}
|
||||
onDismiss={() => setMediaModalOpen(false)}
|
||||
open={mediaModalOpen}
|
||||
liveRegion={() => document.getElementById('flash_screenreader_holder')}
|
||||
languages={languages}
|
||||
/>
|
||||
<Billboard
|
||||
heading={I18n.t('Add Media')}
|
||||
hero={<IconAttachMediaLine color="brand" />}
|
||||
message={
|
||||
<Button size="small" variant="primary" onClick={() => setMediaModalOpen(true)}>
|
||||
{I18n.t('Record/Upload')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
onDismiss = mediaObject => {
|
||||
this.setState({mediaModalOpen: false, mediaObjectUrl: mediaObject.embedded_iframe_url})
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.mediaObjectUrl) {
|
||||
// TODO: figure out how the heck we want to style this thing.
|
||||
return <iframe title="meidathings" src={this.state.mediaObjectUrl} />
|
||||
}
|
||||
|
||||
return (
|
||||
<View as="div" borderWidth="small">
|
||||
<UploadMedia
|
||||
onDismiss={this.onDismiss}
|
||||
contextId={this.props.assignment.env.courseId}
|
||||
contextType="course"
|
||||
open={this.state.mediaModalOpen}
|
||||
tabs={{embed: false, record: true, upload: true}}
|
||||
uploadMediaTranslations={{UploadMediaStrings, MediaCaptureStrings}}
|
||||
liveRegion={() => document.getElementById('flash_screenreader_holder')}
|
||||
languages={languages}
|
||||
/>
|
||||
<Billboard
|
||||
heading={I18n.t('Add Media')}
|
||||
hero={<IconAttachMediaLine color="brand" />}
|
||||
message={
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
onClick={() => this.setState({mediaModalOpen: true})}
|
||||
>
|
||||
{I18n.t('Record/Upload')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"@instructure/ui-themes": "^6.8.1",
|
||||
"@instructure/ui-toggle-details": "^6.8.1",
|
||||
"@instructure/uid": "^6.8.1",
|
||||
"axios": "^0.18.0",
|
||||
"prop-types": "^15",
|
||||
"react": "^16",
|
||||
"react-dom": "^16"
|
||||
|
|
|
@ -26,6 +26,7 @@ export default function EmbedPanel({embedCode, setEmbedCode, label}) {
|
|||
maxHeight="10rem"
|
||||
label={label}
|
||||
value={embedCode}
|
||||
placeholder={label}
|
||||
onChange={e => setEmbedCode(e.target.value)}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -20,12 +20,14 @@ import React from 'react'
|
|||
|
||||
import {Alert} from '@instructure/ui-alerts'
|
||||
import {canUseMediaCapture, MediaCapture} from '@instructure/media-capture'
|
||||
import {object, string} from 'prop-types'
|
||||
import {func, object, string} from 'prop-types'
|
||||
|
||||
import saveMediaRecording from './saveMediaRecording'
|
||||
|
||||
export default class MediaRecorder extends React.Component {
|
||||
// saveFile = (file) => {
|
||||
// this.props.contentProps.saveMediaRecording(file, this.props.editor, this.props.dismiss)
|
||||
// }
|
||||
saveFile = file => {
|
||||
saveMediaRecording(file, this.props.contextId, this.props.contextType, this.props.dismiss)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -43,6 +45,9 @@ export default class MediaRecorder extends React.Component {
|
|||
}
|
||||
|
||||
MediaRecorder.propTypes = {
|
||||
contextId: string,
|
||||
contextType: string,
|
||||
dismiss: func,
|
||||
errorMessage: string.isRequired,
|
||||
MediaCaptureStrings: object // eslint-disable-line react/forbid-prop-types
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const screenfull = {
|
||||
on() {},
|
||||
off() {}
|
||||
}
|
||||
export default screenfull
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {fireEvent, render} from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
import EmbedPanel from '../EmbedPanel'
|
||||
|
||||
describe('EmbedPanel', () => {
|
||||
it('renders with label', () => {
|
||||
const {getByText} = render(
|
||||
<EmbedPanel embedCode="" label="embed panel" setEmbedCode={() => {}} />
|
||||
)
|
||||
expect(getByText('embed panel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders with value', () => {
|
||||
const {getByText} = render(
|
||||
<EmbedPanel embedCode="the best value of the embed" label="" setEmbedCode={() => {}} />
|
||||
)
|
||||
expect(getByText('the best value of the embed')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('on change calls setEmbedCode', () => {
|
||||
const handleChange = jest.fn()
|
||||
const {getByPlaceholderText} = render(
|
||||
<EmbedPanel
|
||||
embedCode="the best value of the embed"
|
||||
label="embed label"
|
||||
setEmbedCode={handleChange}
|
||||
/>
|
||||
)
|
||||
const textArea = getByPlaceholderText('embed label')
|
||||
fireEvent.change(textArea, {target: {value: 'TEST VALUE'}})
|
||||
|
||||
expect(handleChange).toHaveBeenCalledTimes(1)
|
||||
expect(handleChange.mock.calls[0][0]).toBe('TEST VALUE')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, fireEvent, waitForElement} from '@testing-library/react'
|
||||
import ComputerPanel from '../ComputerPanel'
|
||||
|
||||
function makeTranslationProps() {
|
||||
return {
|
||||
UploadMediaStrings: {
|
||||
CLEAR_FILE_TEXT: 'Clear File',
|
||||
INVALID_FILE_TEXT: 'Invalid file type',
|
||||
DRAG_DROP_CLICK_TO_BROWSE: 'drag and drop or clik to browse'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('UploadFile: ComputerPanel', () => {
|
||||
it('shows a failure message if the file is rejected', () => {
|
||||
const notAnImageFile = new File(['foo'], 'foo.txt', {
|
||||
type: 'text/plain'
|
||||
})
|
||||
const handleSetFile = jest.fn()
|
||||
const handleSetHasUploadedFile = jest.fn()
|
||||
const {getByLabelText, getByText} = render(
|
||||
<ComputerPanel
|
||||
theFile={null}
|
||||
setFile={handleSetFile}
|
||||
hasUploadedFile={false}
|
||||
setHasUploadedFile={handleSetHasUploadedFile}
|
||||
accept="image/*"
|
||||
uploadMediaTranslations={makeTranslationProps()}
|
||||
label="Upload File"
|
||||
/>
|
||||
)
|
||||
const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
|
||||
fireEvent.change(dropZone, {
|
||||
target: {
|
||||
files: [notAnImageFile]
|
||||
}
|
||||
})
|
||||
expect(getByText('Invalid file type')).toBeVisible()
|
||||
})
|
||||
|
||||
it('accepts file files', () => {
|
||||
const aFile = new File(['foo'], 'foo.png', {
|
||||
type: 'image/png'
|
||||
})
|
||||
const handleSetFile = jest.fn()
|
||||
const handleSetHasUploadedFile = jest.fn()
|
||||
const {getByLabelText, queryByText} = render(
|
||||
<ComputerPanel
|
||||
theFile={null}
|
||||
setFile={handleSetFile}
|
||||
hasUploadedFile={false}
|
||||
setHasUploadedFile={handleSetHasUploadedFile}
|
||||
accept="image/*"
|
||||
uploadMediaTranslations={makeTranslationProps()}
|
||||
label="Upload File"
|
||||
/>
|
||||
)
|
||||
const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
|
||||
fireEvent.change(dropZone, {
|
||||
target: {
|
||||
files: [aFile]
|
||||
}
|
||||
})
|
||||
expect(queryByText('Invalid file type')).toBeNull()
|
||||
})
|
||||
|
||||
it('clears error messages if a valid file is added', () => {
|
||||
const notAnImageFile = new File(['foo'], 'foo.txt', {
|
||||
type: 'text/plain'
|
||||
})
|
||||
const aFile = new File(['foo'], 'foo.png', {
|
||||
type: 'image/png'
|
||||
})
|
||||
const handleSetFile = jest.fn()
|
||||
const handleSetHasUploadedFile = jest.fn()
|
||||
const {getByLabelText, getByText, queryByText} = render(
|
||||
<ComputerPanel
|
||||
theFile={null}
|
||||
setFile={handleSetFile}
|
||||
hasUploadedFile={false}
|
||||
setHasUploadedFile={handleSetHasUploadedFile}
|
||||
uploadMediaTranslations={makeTranslationProps()}
|
||||
accept="image/*"
|
||||
label="Upload File"
|
||||
/>
|
||||
)
|
||||
const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
|
||||
fireEvent.change(dropZone, {
|
||||
target: {
|
||||
files: [notAnImageFile]
|
||||
}
|
||||
})
|
||||
expect(getByText('Invalid file type')).toBeVisible()
|
||||
fireEvent.change(dropZone, {
|
||||
target: {
|
||||
files: [aFile]
|
||||
}
|
||||
})
|
||||
|
||||
expect(queryByText('Invalid file type')).toBeNull()
|
||||
})
|
||||
|
||||
it('Renders a video player preview if a file type is a video', async () => {
|
||||
global.URL.createObjectURL = jest.fn(() => 'www.blah.com')
|
||||
const handleSetFile = jest.fn()
|
||||
const handleSetHasUploadedFile = jest.fn()
|
||||
const aFile = new File(['foo'], 'foo.mp4', {
|
||||
type: 'video/mp4'
|
||||
})
|
||||
const {getByText} = render(
|
||||
<ComputerPanel
|
||||
theFile={aFile}
|
||||
setFile={handleSetFile}
|
||||
uploadMediaTranslations={makeTranslationProps()}
|
||||
hasUploadedFile
|
||||
setHasUploadedFile={handleSetHasUploadedFile}
|
||||
accept="mp4"
|
||||
label="Upload File"
|
||||
/>
|
||||
)
|
||||
const playButton = await waitForElement(() => getByText('Play'))
|
||||
expect(playButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import K5Uploader from '@instructure/k5uploader'
|
||||
import moxios from 'moxios'
|
||||
import sinon from 'sinon'
|
||||
import saveMediaRecording from '../saveMediaRecording'
|
||||
|
||||
function mediaServerSession() {
|
||||
return {
|
||||
ks: 'averylongstring',
|
||||
subp_id: '0',
|
||||
partner_id: '9',
|
||||
uid: '1234_567',
|
||||
serverTime: 1234,
|
||||
kaltura_setting: {
|
||||
uploadUrl: 'url.url.url',
|
||||
entryUrl: 'url.url.url',
|
||||
uiconfUrl: 'url.url.url',
|
||||
partnerData: 'data from our partners'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('saveMediaRecording', () => {
|
||||
beforeEach(() => {
|
||||
moxios.install()
|
||||
})
|
||||
afterEach(() => {
|
||||
moxios.uninstall()
|
||||
})
|
||||
it('fails if request for kaltura session fails', async done => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 500,
|
||||
response: {error: 'womp womp'}
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
sinon.stub(K5Uploader.prototype, 'loadUiConf').callsFake(() => 'mock')
|
||||
await saveMediaRecording({}, '1', 'course', doneFunction)
|
||||
expect(doneFunction).toHaveBeenCalledTimes(1)
|
||||
expect(doneFunction.mock.calls[0][0].message).toBe('Request failed with status code 500')
|
||||
done()
|
||||
})
|
||||
|
||||
it('returns error if k5.filreError is dispatched', () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
return saveMediaRecording({}, '1', 'course', doneFunction).then(uploader => {
|
||||
uploader.dispatchEvent('K5.fileError', {error: 'womp womp'}, uploader)
|
||||
expect(doneFunction).toHaveBeenCalledTimes(1)
|
||||
expect(doneFunction.mock.calls[0][0].error).toBe('womp womp')
|
||||
})
|
||||
})
|
||||
|
||||
it('k5.ready calls uploaders uploadFile with file', () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
const uploadFileFunc = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction).then(uploader => {
|
||||
uploader.uploadFile = uploadFileFunc
|
||||
uploader.dispatchEvent('K5.ready', uploader)
|
||||
expect(uploadFileFunc).toHaveBeenCalledTimes(1)
|
||||
expect(uploadFileFunc.mock.calls[0][0].file).toBe('thing')
|
||||
})
|
||||
})
|
||||
|
||||
it('k5.complete calls done with canvasMediaObject data if succeeds', () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
moxios.stubRequest('/api/v1/media_objects', {
|
||||
status: 200,
|
||||
response: {data: 'media object data'}
|
||||
})
|
||||
const doneFunction2 = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
|
||||
async uploader => {
|
||||
uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
|
||||
await new Promise(setTimeout)
|
||||
expect(doneFunction2).toHaveBeenCalledTimes(1)
|
||||
expect(doneFunction2.mock.calls[0][0].data).toBe('media object data')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('fails if request to create media object fails', async () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
moxios.stubRequest('/api/v1/media_objects', {
|
||||
status: 500,
|
||||
response: {error: 'womp womp'}
|
||||
})
|
||||
const doneFunction2 = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
|
||||
async uploader => {
|
||||
uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
|
||||
await new Promise(setTimeout)
|
||||
expect(doneFunction2).toHaveBeenCalledTimes(1)
|
||||
expect(doneFunction2.mock.calls[0][0].message).toBe('Request failed with status code 500')
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
|
@ -25,6 +25,7 @@ import {Tabs} from '@instructure/ui-tabs'
|
|||
import {View} from '@instructure/ui-layout'
|
||||
|
||||
import {ACCEPTED_FILE_TYPES} from './acceptedMediaFileTypes'
|
||||
import saveMediaRecording from './saveMediaRecording'
|
||||
import translationShape from './translationShape'
|
||||
|
||||
const ClosedCaptionPanel = React.lazy(() => import('./ClosedCaptionPanel'))
|
||||
|
@ -39,24 +40,6 @@ export const PANELS = {
|
|||
CLOSED_CAPTIONS: 3
|
||||
}
|
||||
|
||||
export const handleSubmit = (editor, selectedPanel, uploadData, saveMediaRecording, onDismiss) => {
|
||||
switch (selectedPanel) {
|
||||
case PANELS.COMPUTER: {
|
||||
const {theFile} = uploadData
|
||||
saveMediaRecording(theFile, editor, onDismiss)
|
||||
break
|
||||
}
|
||||
case PANELS.EMBED: {
|
||||
const {embedCode} = uploadData
|
||||
editor.insertContent(embedCode)
|
||||
onDismiss()
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error('Selected Panel is invalid') // Should never get here
|
||||
}
|
||||
}
|
||||
|
||||
export default class UploadMedia extends React.Component {
|
||||
static propTypes = {
|
||||
languages: arrayOf(
|
||||
|
@ -66,8 +49,15 @@ export default class UploadMedia extends React.Component {
|
|||
})
|
||||
),
|
||||
liveRegion: func,
|
||||
contextId: string,
|
||||
contextType: string,
|
||||
onDismiss: func,
|
||||
open: bool,
|
||||
tabs: shape({
|
||||
embed: bool,
|
||||
record: bool,
|
||||
upload: bool
|
||||
}),
|
||||
uploadMediaTranslations: translationShape
|
||||
}
|
||||
|
||||
|
@ -78,6 +68,26 @@ export default class UploadMedia extends React.Component {
|
|||
theFile: null
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
switch (this.state.selectedPanel) {
|
||||
case PANELS.COMPUTER: {
|
||||
saveMediaRecording(
|
||||
this.state.theFile,
|
||||
this.props.contextId,
|
||||
this.props.contextType,
|
||||
this.props.onDismiss
|
||||
)
|
||||
break
|
||||
}
|
||||
case PANELS.EMBED: {
|
||||
this.props.onDismiss()
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error('Selected Panel is invalid') // Should never get here
|
||||
}
|
||||
}
|
||||
|
||||
renderFallbackSpinner = () => {
|
||||
const {LOADING_MEDIA} = this.props.uploadMediaTranslations.UploadMediaStrings
|
||||
return (
|
||||
|
@ -104,51 +114,56 @@ export default class UploadMedia extends React.Component {
|
|||
maxWidth="large"
|
||||
onRequestTabChange={(_, {index}) => this.setState({selectedPanel: index})}
|
||||
>
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.COMPUTER}
|
||||
renderTitle={() => COMPUTER_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<ComputerPanel
|
||||
theFile={this.state.theFile}
|
||||
setFile={file => this.setState({theFile: file})}
|
||||
hasUploadedFile={this.state.hasUploadedFile}
|
||||
setHasUploadedFile={uploadFileState =>
|
||||
this.setState({hasUploadedFile: uploadFileState})
|
||||
}
|
||||
label={DRAG_FILE_TEXT}
|
||||
uploadMediaTranslations={this.props.uploadMediaTranslations}
|
||||
accept={ACCEPTED_FILE_TYPES}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.RECORD}
|
||||
renderTitle={() => RECORD_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<MediaRecorder
|
||||
MediaCaptureStrings={this.props.uploadMediaTranslations.MediaCaptureStrings}
|
||||
errorMessage={UPLOADING_ERROR}
|
||||
dismiss={this.props.onDismiss}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.EMBED}
|
||||
renderTitle={() => EMBED_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<EmbedPanel
|
||||
label={EMBED_VIDEO_CODE_TEXT}
|
||||
embedCode={this.state.embedCode}
|
||||
setEmbedCode={embedCode => this.setState({embedCode})}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
|
||||
{this.props.tabs.upload && (
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.COMPUTER}
|
||||
renderTitle={() => COMPUTER_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<ComputerPanel
|
||||
theFile={this.state.theFile}
|
||||
setFile={file => this.setState({theFile: file})}
|
||||
hasUploadedFile={this.state.hasUploadedFile}
|
||||
setHasUploadedFile={uploadFileState =>
|
||||
this.setState({hasUploadedFile: uploadFileState})
|
||||
}
|
||||
label={DRAG_FILE_TEXT}
|
||||
uploadMediaTranslations={this.props.uploadMediaTranslations}
|
||||
accept={ACCEPTED_FILE_TYPES}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
{this.props.tabs.record && (
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.RECORD}
|
||||
renderTitle={() => RECORD_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<MediaRecorder
|
||||
MediaCaptureStrings={this.props.uploadMediaTranslations.MediaCaptureStrings}
|
||||
contextType={this.props.contextType}
|
||||
contextId={this.props.contextId}
|
||||
errorMessage={UPLOADING_ERROR}
|
||||
dismiss={this.props.onDismiss}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
{this.props.tabs.embed && (
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.EMBED}
|
||||
renderTitle={() => EMBED_PANEL_TITLE}
|
||||
>
|
||||
<Suspense fallback={this.renderFallbackSpinner()}>
|
||||
<EmbedPanel
|
||||
label={EMBED_VIDEO_CODE_TEXT}
|
||||
embedCode={this.state.embedCode}
|
||||
setEmbedCode={embedCode => this.setState({embedCode})}
|
||||
/>
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
<Tabs.Panel
|
||||
isSelected={this.state.selectedPanel === PANELS.CLOSED_CAPTIONS}
|
||||
renderTitle={() => CLOSED_CAPTIONS_PANEL_TITLE}
|
||||
|
@ -178,7 +193,7 @@ export default class UploadMedia extends React.Component {
|
|||
<Button
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
handleSubmit()
|
||||
this.handleSubmit()
|
||||
}}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import K5Uploader from '@instructure/k5uploader'
|
||||
|
||||
export const VIDEO_SIZE_OPTIONS = {height: '432px', width: '768px'}
|
||||
|
||||
function generateUploadOptions(mediatypes, sessionData) {
|
||||
const sessionDataCopy = JSON.parse(JSON.stringify(sessionData))
|
||||
delete sessionDataCopy.kaltura_setting
|
||||
return {
|
||||
kaltura_session: sessionDataCopy,
|
||||
allowedMediaTypes: mediatypes,
|
||||
uploadUrl: sessionData.kaltura_setting.uploadUrl,
|
||||
entryUrl: sessionData.kaltura_setting.entryUrl,
|
||||
uiconfUrl: sessionData.kaltura_setting.uiconfUrl,
|
||||
entryDefaults: {
|
||||
partnerData: sessionData.kaltura_setting.partner_data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addUploaderReadyEventListeners(uploader, file) {
|
||||
uploader.addEventListener('K5.ready', () => {
|
||||
uploader.uploadFile(file)
|
||||
})
|
||||
}
|
||||
|
||||
function addUploaderFileErrorEventListeners(uploader, done) {
|
||||
uploader.addEventListener('K5.fileError', error => {
|
||||
done(error)
|
||||
})
|
||||
}
|
||||
|
||||
function addUploaderFileCompleteEventListeners(uploader, context, done) {
|
||||
uploader.addEventListener('K5.complete', async mediaServerMediaObject => {
|
||||
mediaServerMediaObject.contextCode = `${context.contextType}_${context.contextId}`
|
||||
mediaServerMediaObject.type = `${context.contextType}_${context.contextId}`
|
||||
|
||||
const body = {
|
||||
id: mediaServerMediaObject.entryId,
|
||||
type:
|
||||
{2: 'image', 5: 'audio'}[mediaServerMediaObject.mediaType] ||
|
||||
mediaServerMediaObject.type.includes('audio')
|
||||
? 'audio'
|
||||
: 'video',
|
||||
context_code: mediaServerMediaObject.contextCode,
|
||||
title: mediaServerMediaObject.title,
|
||||
user_entered_title: mediaServerMediaObject.userTitle
|
||||
}
|
||||
|
||||
try {
|
||||
const canvasMediaObject = await axios.post('/api/v1/media_objects', body)
|
||||
done(canvasMediaObject.data)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default async function saveMediaRecording(file, contextId, contextType, done) {
|
||||
try {
|
||||
const mediaServerSession = await axios.post(
|
||||
'/api/v1/services/kaltura_session?include_upload_config=1'
|
||||
)
|
||||
const session = generateUploadOptions(
|
||||
['video', 'audio', 'webm', 'video/webm', 'audio/webm'],
|
||||
mediaServerSession.data
|
||||
)
|
||||
const k5UploaderSession = new K5Uploader(session)
|
||||
addUploaderReadyEventListeners(k5UploaderSession, file)
|
||||
addUploaderFileErrorEventListeners(k5UploaderSession, done)
|
||||
addUploaderFileCompleteEventListeners(k5UploaderSession, {contextId, contextType}, done)
|
||||
return k5UploaderSession
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue