Skip to content
This repository was archived by the owner on Jan 5, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
"is-absolute-url": "^2.0.0",
"isomorphic-fetch": "^2.2.1",
"lodash.isequal": "^4.4.0",
"mediaelement": "^4.0.2",
"number-with-commas": "^1.1.0",
"object-assign": "^4.1.0",
"openseadragon": "^2.2.1",
"plyr": "^2.0.12",
"promise-queue": "^2.2.3",
"rc-slider": "^5.1.2",
"react": "^15.4.2",
Expand Down Expand Up @@ -75,6 +77,7 @@
"babel-preset-stage-0": "^6.16.0",
"babel-runtime": "^6.20.0",
"chai": "^3.5.0",
"chai-enzyme": "^0.6.1",
"enzyme": "^2.4.1",
"eslint": "^3.2.2",
"eslint-plugin-react": "^6.0.0",
Expand Down
35 changes: 35 additions & 0 deletions src/components/media/Audio.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'

class Audio extends React.Component {

constructor (props) {
super(props)
}

render() {
const props = this.props
return (
<audio {...props}>
{
props.sources.map(source => {
return <source {...source} />
})
}
</audio>
)
}
}

Audio.propTypes = {
options: React.PropTypes.object,
poster: React.PropTypes.string,
src: React.PropTypes.string,
type: React.PropTypes.string.isRequired,
sources: React.PropTypes.array,
}

Audio.defaultProps = {
sources: []
}

export default Audio
70 changes: 70 additions & 0 deletions src/components/media/MediaPlayer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react'
import plyr from 'plyr'
import Video from './Video.jsx'
import Audio from './Audio.jsx'
import assign from 'object-assign'
/**
* Component for playing streaming media resources
*
*/
class MediaPlayer extends React.Component {

// Constructor for the class
// @params
constructor (props) {
super(props)
this.state = {
mediaComponent: null
}
}

// Initialize the global `plyr` Object after the component has been mounted
componentDidMount () {
plyr.setup(this.props.options)
}

// Render the media element component (if it exists)
renderMediaElement() {
const typeSegments = this.props.type.split('/')
if ( typeSegments.length == 0 )
throw 'No content type specified for the media player: ' + this.props.type
const format = typeSegments[0]
const props = assign({}, {
type: this.props.type,
sources: this.props.sources,
captions: this.props.captions,
src: this.props.src,
poster: this.props.poster,
}, this.props.mediaComponentProps)

switch(format) {
case 'video':
return (<Video {...props} />)
break
case 'audio':
return (<Audio {...props} />)
break
default:
throw 'Unsupported content type specified for the media player: ' + this.props.type
}
}

// Render the container for the elements
render() {
return (
<div>
{ this.renderMediaElement() }
</div>
)
}
}

MediaPlayer.propTypes = {
options: React.PropTypes.object,
poster: React.PropTypes.string,
src: React.PropTypes.string,
type: React.PropTypes.string.isRequired,
mediaComponentProps: React.PropTypes.object
}

export default MediaPlayer
62 changes: 62 additions & 0 deletions src/components/media/Video.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'

/**
* Component for HTML5 streaming Video resources
*
*/
class Video extends React.Component {

// Constructor for the class
// @params props
constructor (props) {
super(props)
}

// Generate the elements for the captions in the video
// @see (W3C link here)
captions() {
if (this.props.captions) {
const props = this.props.captions
return (<track {...props} />)
} else {
return false
}
}

// Render the <video> element
// @see (W3C link here)
render() {
//const props = this.props
const props = {src: this.props.src, poster: this.props.poster}
const sources = this.props.sources

return (
<video {...props}>
{
sources.map(source => {
return <source {...source} />
})
}
{ this.captions() }
</video>
)
}
}

Video.propTypes = {
options: React.PropTypes.object,
poster: React.PropTypes.string,
src: React.PropTypes.string,
type: React.PropTypes.string.isRequired,
sources: React.PropTypes.array,
captions: React.PropTypes.object,
}

Video.defaultProps = {
sources: [],
controls: true,
loop: false,
muted: false,
}

export default Video
82 changes: 82 additions & 0 deletions src/components/media/__tests__/Audio-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react'
import { shallow, mount } from 'enzyme'
import chai, { expect } from 'chai'
import chaiEnzyme from 'chai-enzyme'
import assign from 'object-assign'
import Audio from '../Audio.jsx'

const defaultProps = {
options: {
enabled: true,
html: '',
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'],
i18n: {},
loadSprite: true,
iconUrl: null,
iconPrefix: 'plyr',
blankUrl: 'https://cdn.selz.com/plyr/blank.mp4',
debug: false,
autoplay: false,
seekTime: 10,
volume: 5,
clickToPlay: true,
disableContextMenu: true,
hideControls: true,
showPosterOnEnd: false,
keyboardShortcuts: { focused: true, global: false },
tooltips: { controls: false, seek: true },
duration: null,
displayDuration: true,
selectors: {},
listeners: {},
classes: {},
captions: {},
fullscreen: {'enabled': true, 'fallback': true, 'allowAudio': false},
storage: { enabled: true, key: 'plyr_volume' }
},
poster: '//localhost.localdomain/img/poster.png',
src: '//localhost.localdomain/streaming/stream.mp4',
type: 'audio/ogg',
controls: true,
crossOrigin: true,
sources: [{src:"foo.ogg", type:"audio/ogg"}]
}

const noop = () => {}

const wrapEl = (xtend, renderer) => {
const props = assign({}, defaultProps, { onClose: noop }, xtend)

return renderer(<Audio {...props} />)
}

const shallowEl = xtend => wrapEl(xtend, shallow)
const mountEl = xtend => wrapEl(xtend, mount)

describe('<Audio />', () => {
it('renders a Audio element', () => {
const $el = shallowEl()
expect($el.find(Audio)).to.be.a('Object')
expect($el.find(Audio).first()).to.be.a('Object')
})

it('receives the properties for the audio element', function () {
const $el = mountEl()
const player = $el.find(Audio).first()
expect(player).to.have.prop('poster', '//localhost.localdomain/img/poster.png')
expect(player).to.have.prop('src', '//localhost.localdomain/streaming/stream.mp4')
expect(player).to.have.prop('type', 'audio/ogg')
expect(player).to.have.prop('controls', true)
expect(player).to.have.prop('crossOrigin', true)
})

describe('#sources', () => {
it('renders source elements', () => {
const $el = mountEl()
const player = $el.instance()
expect($el).to.have.descendants('source')
})
})
})

chai.use(chaiEnzyme())
95 changes: 95 additions & 0 deletions src/components/media/__tests__/MediaPlayer-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react'
import { shallow, mount, render } from 'enzyme'
import chai, { expect } from 'chai'
import chaiEnzyme from 'chai-enzyme'
import assign from 'object-assign'
import MediaPlayer from '../MediaPlayer.jsx'
import Video from '../Video.jsx'
import Audio from '../Audio.jsx'

const defaultProps = {
options: {
enabled: true,
html: '',
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'],
i18n: {},
loadSprite: true,
iconUrl: null,
iconPrefix: 'plyr',
blankUrl: 'https://cdn.selz.com/plyr/blank.mp4',
debug: false,
autoplay: false,
seekTime: 10,
volume: 5,
clickToPlay: true,
disableContextMenu: true,
hideControls: true,
showPosterOnEnd: false,
keyboardShortcuts: { focused: true, global: false },
tooltips: { controls: false, seek: true },
duration: null,
displayDuration: true,
selectors: {},
listeners: {},
classes: {},
captions: {},
fullscreen: {'enabled': true, 'fallback': true, 'allowAudio': false},
storage: { enabled: true, key: 'plyr_volume' }
},
poster: '//localhost.localdomain/img/poster.png',
src: '//localhost.localdomain/streaming/stream.ogg',
type: 'video/ogg',
controls: true,
crossOrigin: true
}

const noop = () => {}

const wrapEl = (xtend, renderer) => {
const props = assign({}, defaultProps, { onClose: noop }, xtend)

return renderer(<MediaPlayer {...props} />)
}

const shallowEl = xtend => wrapEl(xtend, shallow)
const mountEl = xtend => wrapEl(xtend, mount)
const renderEl = xtend => wrapEl(xtend, render)

describe('<MediaPlayer />', () => {
it('renders a MediaPlayer element', () => {
const $el = shallowEl()
expect($el.find(MediaPlayer)).to.be.a('Object')
expect($el.find(MediaPlayer).first()).to.be.ok
})

it('receives the properties for the video element', function () {
const $el = mountEl()
const player = $el.find(MediaPlayer).first()

expect(player).to.have.prop('poster','//localhost.localdomain/img/poster.png')
expect(player).to.have.prop('src','//localhost.localdomain/streaming/stream.ogg')
expect(player).to.have.prop('type', 'video/ogg')
expect(player).to.have.prop('controls', true)
expect(player).to.have.prop('crossOrigin', true)
})

describe('#renderMediaElement', function () {
it('sets media element to a Video by parsing the media type', function () {
const $el = mountEl()
const player = $el.instance()
expect($el).to.have.descendants('video')
})

it('sets media element to a Audio by parsing the media type', function () {
const $el = mountEl({type: 'audio/ogg'})
const player = $el.instance()
expect($el).to.have.descendants('audio')
})

it('raises an exception when setting a media element to an unsupported media type', function () {
const props = assign({}, defaultProps, {type: 'no-exist/no-exist'})
})
})
})

chai.use(chaiEnzyme())
Loading