add ui-media-player to SpeedGrader submission comments
flag=speedgrader_studio_media_capture test plan: - have a course with at least 1 student and assignnment - go to SpeedGrader with the FF on - record or upload a media submission comment - click the video to open the media player - notice the media player is the new one Change-Id: Ia17ca557130098b9bfaee4c31a4bd2b88c773c11 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/349020 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Spencer Olson <solson@instructure.com> Reviewed-by: Cameron Ray <cameron.ray@instructure.com> QA-Review: Cameron Ray <cameron.ray@instructure.com> Product-Review: Ravi Koll <ravi.koll@instructure.com>
This commit is contained in:
parent
abdc414ee5
commit
7bac8db0db
|
@ -17,12 +17,23 @@
|
|||
|
||||
import {getSourcesAndTracks} from '../mediaComment'
|
||||
import $ from 'jquery'
|
||||
import {enableFetchMocks} from 'jest-fetch-mock'
|
||||
|
||||
enableFetchMocks()
|
||||
|
||||
describe('getSourcesAndTracks', () => {
|
||||
beforeAll(() => {
|
||||
$.getJSON = jest.fn()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
$.getJSON.mockRestore()
|
||||
})
|
||||
|
||||
it('with no attachment id', () => {
|
||||
getSourcesAndTracks(1)
|
||||
expect($.getJSON).toHaveBeenCalledWith('/media_objects/1/info', expect.anything())
|
||||
|
@ -32,4 +43,99 @@ describe('getSourcesAndTracks', () => {
|
|||
getSourcesAndTracks(1, 4)
|
||||
expect($.getJSON).toHaveBeenCalledWith('/media_attachments/4/info', expect.anything())
|
||||
})
|
||||
|
||||
it('should return sources and tracks in the old format when studio_media_capture_enabled is false', async () => {
|
||||
ENV.studio_media_capture_enabled = false
|
||||
// Mock response
|
||||
const mockResponse = {
|
||||
media_sources: [
|
||||
{
|
||||
url: 'http://example.com/video.mp4',
|
||||
content_type: 'video/mp4',
|
||||
width: 640,
|
||||
height: 360,
|
||||
bitrate: 500000,
|
||||
},
|
||||
{
|
||||
url: 'http://example.com/video_low.mp4',
|
||||
content_type: 'video/mp4',
|
||||
width: 320,
|
||||
height: 180,
|
||||
bitrate: 250000,
|
||||
},
|
||||
],
|
||||
media_tracks: [{url: 'http://example.com/track.vtt', kind: 'subtitles', locale: 'en'}],
|
||||
can_add_captions: true,
|
||||
}
|
||||
|
||||
// Mock $.getJSON to return the mock response
|
||||
jest.spyOn($, 'getJSON').mockImplementation((url, callback) => {
|
||||
callback(mockResponse)
|
||||
return $.Deferred().resolve(mockResponse).promise()
|
||||
})
|
||||
|
||||
const id = '123'
|
||||
const result = await getSourcesAndTracks(id)
|
||||
|
||||
expect(result.sources).toEqual([
|
||||
"<source type='video/mp4' src='http://example.com/video_low.mp4' title='320x180 244 kbps' />",
|
||||
"<source type='video/mp4' src='http://example.com/video.mp4' title='640x360 488 kbps' />",
|
||||
])
|
||||
expect(result.tracks).toEqual([
|
||||
"<track kind='subtitles' label='English' src='http://example.com/track.vtt' srclang='en' data-inherited-track='' />",
|
||||
])
|
||||
})
|
||||
|
||||
it('should return sources and tracks in the new format when studio_media_capture_enabled is true', async () => {
|
||||
ENV.studio_media_capture_enabled = true
|
||||
const mockResponse = {
|
||||
media_sources: [
|
||||
{
|
||||
url: 'http://example.com/video.mp4',
|
||||
content_type: 'video/mp4',
|
||||
width: 640,
|
||||
height: 360,
|
||||
bitrate: 500000,
|
||||
},
|
||||
{
|
||||
url: 'http://example.com/video_low.mp4',
|
||||
content_type: 'video/mp4',
|
||||
width: 320,
|
||||
height: 180,
|
||||
bitrate: 250000,
|
||||
},
|
||||
],
|
||||
media_tracks: [{url: 'http://example.com/track.vtt', kind: 'subtitles', locale: 'en'}],
|
||||
can_add_captions: true,
|
||||
}
|
||||
|
||||
// Mock $.getJSON to return the mock response
|
||||
jest.spyOn($, 'getJSON').mockImplementation((url, callback) => {
|
||||
callback(mockResponse)
|
||||
return $.Deferred().resolve(mockResponse).promise()
|
||||
})
|
||||
|
||||
const id = '123'
|
||||
const result = await getSourcesAndTracks(id)
|
||||
|
||||
expect(result.sources).toEqual([
|
||||
{
|
||||
src: 'http://example.com/video_low.mp4',
|
||||
label: '320x180 244 kbps',
|
||||
},
|
||||
{
|
||||
src: 'http://example.com/video.mp4',
|
||||
label: '640x360 488 kbps',
|
||||
},
|
||||
])
|
||||
expect(result.tracks).toEqual([
|
||||
{
|
||||
id: '123',
|
||||
type: 'subtitles',
|
||||
label: 'English',
|
||||
src: '/track.vtt',
|
||||
language: 'en',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -25,6 +25,9 @@ import {map, values} from 'lodash'
|
|||
import htmlEscape from '@instructure/html-escape'
|
||||
import sanitizeUrl from '@canvas/util/sanitizeUrl'
|
||||
import {contentMapping} from '@instructure/canvas-rce/src/common/mimeClass'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {MediaPlayer} from '@instructure/ui-media-player'
|
||||
|
||||
const I18n = useI18nScope('jquery_media_comments')
|
||||
|
||||
|
@ -96,6 +99,7 @@ mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'sourcechoose
|
|||
mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'speed')
|
||||
|
||||
export function getSourcesAndTracks(id, attachmentId) {
|
||||
const studioMediaEnabled = ENV.studio_media_capture_enabled
|
||||
const dfd = new $.Deferred()
|
||||
const api = attachmentId ? 'media_attachments' : 'media_objects'
|
||||
$.getJSON(`/${api}/${attachmentId || id}/info`, data => {
|
||||
|
@ -106,24 +110,39 @@ export function getSourcesAndTracks(id, attachmentId) {
|
|||
// mediaplayer plays the first source by default, which tends to be the highest
|
||||
// resolution. sort so we play the lowest res. by default
|
||||
.sort((a, b) => parseInt(a.bitrate, 10) - parseInt(b.bitrate, 10))
|
||||
.map(
|
||||
source =>
|
||||
// xsslint safeString.function sanitizeUrl
|
||||
`<source
|
||||
type='${htmlEscape(source.content_type)}'
|
||||
src='${sanitizeUrl(htmlEscape(source.url))}'
|
||||
title='${htmlEscape(source.width)}x${htmlEscape(source.height)} ${htmlEscape(
|
||||
Math.floor(source.bitrate / 1024)
|
||||
)} kbps'
|
||||
/>`
|
||||
)
|
||||
.map(source => {
|
||||
if (studioMediaEnabled) {
|
||||
return {
|
||||
src: source.url,
|
||||
label: `${htmlEscape(source.width)}x${htmlEscape(source.height)} ${htmlEscape(
|
||||
Math.floor(source.bitrate / 1024)
|
||||
)} kbps`,
|
||||
}
|
||||
}
|
||||
// xsslint safeString.function sanitizeUrl
|
||||
return `<source type='${htmlEscape(source.content_type)}' src='${sanitizeUrl(
|
||||
htmlEscape(source.url)
|
||||
)}' title='${htmlEscape(source.width)}x${htmlEscape(source.height)} ${htmlEscape(
|
||||
Math.floor(source.bitrate / 1024)
|
||||
)} kbps' />`
|
||||
})
|
||||
|
||||
const tracks = map(data.media_tracks, track => {
|
||||
const languageName = mejs.language.codes[track.locale] || track.locale
|
||||
if (studioMediaEnabled) {
|
||||
return {
|
||||
id: attachmentId || id,
|
||||
type: track.kind,
|
||||
label: languageName,
|
||||
src: getPathFromUrl(track.url),
|
||||
language: track.locale,
|
||||
}
|
||||
}
|
||||
return `<track kind='${htmlEscape(track.kind)}' label='${htmlEscape(
|
||||
languageName
|
||||
)}' src='${htmlEscape(track.url)}' srclang='${htmlEscape(track.locale)}'
|
||||
data-inherited-track='${htmlEscape(track.inherited)}' />`
|
||||
)}' src='${htmlEscape(track.url)}' srclang='${htmlEscape(
|
||||
track.locale
|
||||
)}' data-inherited-track='${htmlEscape(track.inherited)}' />`
|
||||
})
|
||||
|
||||
const types = map(data.media_sources, source => source.content_type)
|
||||
|
@ -132,6 +151,11 @@ export function getSourcesAndTracks(id, attachmentId) {
|
|||
return dfd
|
||||
}
|
||||
|
||||
function getPathFromUrl(url) {
|
||||
const urlObj = new URL(url)
|
||||
return urlObj.pathname + urlObj.search + urlObj.hash
|
||||
}
|
||||
|
||||
function createMediaTag({sourcesAndTracks, mediaType, height, width, mediaPlayerOptions}) {
|
||||
let tagType = mediaType === 'video' ? 'video' : 'audio'
|
||||
const st_tags = sourcesAndTracks.sources.concat(sourcesAndTracks.tracks).join('')
|
||||
|
@ -328,6 +352,14 @@ const mediaCommentActions = {
|
|||
mediaCommentId: id,
|
||||
}
|
||||
|
||||
const mediaPlayer = (
|
||||
<MediaPlayer
|
||||
tracks={sourcesAndTracks.tracks}
|
||||
sources={sourcesAndTracks.sources}
|
||||
captionPosition="bottom"
|
||||
/>
|
||||
)
|
||||
|
||||
const $mediaTag = createMediaTag({
|
||||
sourcesAndTracks,
|
||||
mediaPlayerOptions,
|
||||
|
@ -335,7 +367,13 @@ const mediaCommentActions = {
|
|||
height,
|
||||
width,
|
||||
})
|
||||
$mediaTag.appendTo($dialog.html(''))
|
||||
|
||||
const studioMediaEnabled = ENV.studio_media_capture_enabled
|
||||
if (studioMediaEnabled) {
|
||||
ReactDOM.render(mediaPlayer, $dialog[0])
|
||||
} else {
|
||||
$mediaTag.appendTo($dialog.html(''))
|
||||
}
|
||||
|
||||
$this.data({
|
||||
mediaelementplayer: new mejs.MediaElementPlayer($mediaTag, mediaPlayerOptions),
|
Loading…
Reference in New Issue