From cc1639eadec00b8d2b6e7f510cdc83341289715c Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Tue, 20 Jun 2017 19:11:51 -0400 Subject: [PATCH] Add compatibility layer for iOS 11 --- package.json | 2 +- src/client/__mocks__/window.js | 6 +- src/client/__tests__/App-test.js | 11 ++- src/client/__tests__/window-test.js | 10 +++ .../actions/__tests__/SocketActions-test.js | 10 ++- src/client/components/App.js | 4 +- src/client/components/Input.js | 1 + src/client/components/Video.js | 30 +++++++- src/client/components/__tests__/Video-test.js | 76 +++++++++++++++++++ src/client/reducers/__tests__/streams-test.js | 29 ++++++- src/client/reducers/streams.js | 34 +++++++-- src/client/window.js | 3 + src/scss/style.scss | 8 +- 13 files changed, 200 insertions(+), 24 deletions(-) create mode 100644 src/client/components/__tests__/Video-test.js diff --git a/package.json b/package.json index 6f69397..90d5a5a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "js": "browserify -t babelify ./src/client/index.js -o ./build/index.js", "js:watch": "watchify -d -v -t babelify ./src/client/index.js -o ./build/index.js", "css": "node-sass ./src/scss/style.scss -o ./build/", - "css:watch": "node-sass --watch ./src/scss/style.scss -o ./build/", + "css:watch": "npm run css && node-sass --watch ./src/scss/style.scss -o ./build/", "lint": "eslint .", "lint:fix": "eslint . --fix", "ci": "npm run lint && npm run test:coverage && npm run build" diff --git a/src/client/__mocks__/window.js b/src/client/__mocks__/window.js index 62fe787..e589b8f 100644 --- a/src/client/__mocks__/window.js +++ b/src/client/__mocks__/window.js @@ -1,8 +1,10 @@ import Promise from 'bluebird' -export const createObjectURL = object => 'blob://' + String(object) +export const createObjectURL = jest.fn() +.mockImplementation(object => 'blob://' + String(object)) +export const revokeObjectURL = jest.fn() -class MediaStream {} +export class MediaStream {} export function getUserMedia () { return !getUserMedia.shouldFail ? Promise.resolve(getUserMedia.stream) diff --git a/src/client/__tests__/App-test.js b/src/client/__tests__/App-test.js index b5f485e..036e5df 100644 --- a/src/client/__tests__/App-test.js +++ b/src/client/__tests__/App-test.js @@ -1,5 +1,6 @@ jest.mock('../actions/CallActions.js') jest.mock('../socket.js') +jest.mock('../window.js') import * as constants from '../constants.js' import App from '../containers/App.js' @@ -8,6 +9,7 @@ import ReactDOM from 'react-dom' import TestUtils from 'react-dom/test-utils' import configureStore from 'redux-mock-store' import reducers from '../reducers' +import { MediaStream } from '../window.js' import { Provider } from 'react-redux' import { init } from '../actions/CallActions.js' import { middlewares } from '../store.js' @@ -44,9 +46,12 @@ describe('App', () => { describe('state', () => { let alert beforeEach(() => { - state.streams = state.streams.merge({ - test: 'blob://' - }) + state.streams = { + test: { + mediaStream: new MediaStream(), + url: 'blob://' + } + } state.peers = { test: {} } diff --git a/src/client/__tests__/window-test.js b/src/client/__tests__/window-test.js index 30f0531..097f473 100644 --- a/src/client/__tests__/window-test.js +++ b/src/client/__tests__/window-test.js @@ -2,6 +2,7 @@ import Promise from 'bluebird' import { createObjectURL, + revokeObjectURL, getUserMedia, navigator, play, @@ -100,6 +101,15 @@ describe('window', () => { }) + describe('createObjectURL', () => { + + it('calls window.URL.revokeObjectURL', () => { + window.URL.revokeObjectURL = jest.fn() + expect(revokeObjectURL()).toBe(undefined) + }) + + }) + describe('valueOf', () => { let input diff --git a/src/client/actions/__tests__/SocketActions-test.js b/src/client/actions/__tests__/SocketActions-test.js index 2d99b1d..eb5f2e5 100644 --- a/src/client/actions/__tests__/SocketActions-test.js +++ b/src/client/actions/__tests__/SocketActions-test.js @@ -124,7 +124,10 @@ describe('SocketActions', () => { peer.emit(constants.PEER_EVENT_STREAM, stream) expect(store.getState().streams).toEqual({ - b: jasmine.any(String) + b: { + mediaStream: stream, + url: jasmine.any(String) + } }) }) }) @@ -134,7 +137,10 @@ describe('SocketActions', () => { const stream = {} peer.emit(constants.PEER_EVENT_STREAM, stream) expect(store.getState().streams).toEqual({ - b: jasmine.any(String) + b: { + mediaStream: stream, + url: jasmine.any(String) + } }) }) diff --git a/src/client/components/App.js b/src/client/components/App.js index 0e6fbb8..50db8bf 100644 --- a/src/client/components/App.js +++ b/src/client/components/App.js @@ -4,7 +4,7 @@ import Input from './Input.js' import Notifications, { NotificationPropTypes } from './Notifications.js' import PropTypes from 'prop-types' import React from 'react' -import Video from './Video.js' +import Video, { StreamPropType } from './Video.js' import _ from 'underscore' export default class App extends React.PureComponent { @@ -17,7 +17,7 @@ export default class App extends React.PureComponent { notify: PropTypes.func.isRequired, peers: PropTypes.object.isRequired, sendMessage: PropTypes.func.isRequired, - streams: PropTypes.objectOf(PropTypes.string).isRequired, + streams: PropTypes.objectOf(StreamPropType).isRequired, toggleActive: PropTypes.func.isRequired } componentDidMount () { diff --git a/src/client/components/Input.js b/src/client/components/Input.js index bdade9a..2c5e61d 100644 --- a/src/client/components/Input.js +++ b/src/client/components/Input.js @@ -45,6 +45,7 @@ export default class Input extends React.PureComponent { type="text" value={message} /> + ) } diff --git a/src/client/components/Video.js b/src/client/components/Video.js index 80f2cce..97d7034 100644 --- a/src/client/components/Video.js +++ b/src/client/components/Video.js @@ -2,12 +2,18 @@ import PropTypes from 'prop-types' import React from 'react' import classnames from 'classnames' import { ME } from '../constants.js' +import { MediaStream } from '../window.js' + +export const StreamPropType = PropTypes.shape({ + mediaStream: PropTypes.instanceOf(MediaStream).isRequired, + url: PropTypes.string +}) export default class Video extends React.PureComponent { static propTypes = { onClick: PropTypes.func, active: PropTypes.bool.isRequired, - stream: PropTypes.string, + stream: StreamPropType, userId: PropTypes.string.isRequired } handleClick = e => { @@ -19,8 +25,26 @@ export default class Video extends React.PureComponent { e.preventDefault() e.target.play() } + componentDidMount () { + this.componentDidUpdate() + } + componentDidUpdate () { + const { stream } = this.props + const { video } = this.refs + const mediaStream = stream && stream.mediaStream + const url = stream && stream.url + if ('srcObject' in video) { + if (video.srcObject !== mediaStream) { + this.refs.video.srcObject = mediaStream + } + } else { + if (video.src !== url) { + video.src = url + } + } + } render () { - const { active, stream, userId } = this.props + const { active, userId } = this.props const className = classnames('video-container', { active }) return (
@@ -28,7 +52,7 @@ export default class Video extends React.PureComponent { muted={userId === ME} onClick={this.handleClick} onLoadedMetadata={this.play} - src={stream} + ref="video" />
) diff --git a/src/client/components/__tests__/Video-test.js b/src/client/components/__tests__/Video-test.js new file mode 100644 index 0000000..9047362 --- /dev/null +++ b/src/client/components/__tests__/Video-test.js @@ -0,0 +1,76 @@ +jest.mock('../../window.js') +import React from 'react' +import TestUtils from 'react-dom/test-utils' +import Video from '../Video.js' +import { MediaStream } from '../../window.js' + +describe('components/Video', () => { + + class VideoWrapper extends React.PureComponent { + static propTypes = Video.propTypes + constructor () { + super() + this.state = {} + } + render () { + return