Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add Chromecast Support #41

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
9 changes: 8 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@
]
}
}]
]
],
"env": {
"test": {
"presets": [
"next/babel"
]
}
}
}
66 changes: 66 additions & 0 deletions components/ChromecastButton/ChromecastButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';

class ChromecastButton extends React.Component {
static defaultProps = {
context: global.window,
initializerNamespace: '__onGCastApiAvailable',
scriptSrc: 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1',
uniqueScriptId: 'relisten-chromecast',
}

constructor(props) {
super(props);

this.initializeChromecast = this.initializeChromecast.bind(this);
this.installPlayerScript = this.installPlayerScript.bind(this);
this.mountChromecastInitializer = this.mountChromecastInitializer.bind(this);
}

componentDidMount() {
this.mountChromecastInitializer();
this.installPlayerScript();
}

mountChromecastInitializer() {
this.props.context[this.props.initializerNamespace] = this.initializeChromecast;
}

initializeChromecast(isChromecastAvailable) {
if (isChromecastAvailable) {
const { cast, chrome } = this.props.context;

cast.framework.CastContext.getInstance().setOptions({
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
});
}
}

installPlayerScript() {
const script = this.props.context.document.createElement('script');
script.id = this.props.uniqueScriptId;
script.src = this.props.scriptSrc;
this.props.context.document.head.appendChild(script);
}

render() {
return (
<div className='chromecast-button'>
<style jsx>{`
.chromecast-button {
align-items: center;
cursor: pointer;
display: flex;
width: 20px;
}
`}</style>
<div
dangerouslySetInnerHTML={{
__html: '<google-cast-launcher></google-cast-launcher>'
}}
/>
</div>
);
}
};

export default ChromecastButton;
85 changes: 85 additions & 0 deletions components/ChromecastButton/ChromecastButton.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import jsdom from 'jsdom';
import React from 'react';
import ReactDOM from 'react-dom';
import test from 'tape';

import ChromecastButton from './ChromecastButton';

const { JSDOM } = jsdom;

test('<ChromecastButton />', (t) => {
const { window } = new JSDOM('<!DOCTYPE html><div><div />');
global.window = window;

const initializerNamespace = '__test';
const scriptSrc = 'https://localhost/test.js';
const uniqueScriptId = 'test-id';

const container = window.document.createElement('div');
ReactDOM.render(
<ChromecastButton
initializerNamespace={initializerNamespace}
context={window}
scriptSrc={scriptSrc}
uniqueScriptId={uniqueScriptId}
/>,
container
);
t.true(
container.innerHTML.includes(
'<google-cast-launcher></google-cast-launcher>',
),
'it renders the proper html for the chromecast sdk',
);

const script = window.document.querySelector(`#${uniqueScriptId}`);
t.equals(
script.src,
scriptSrc,
'it loads the supplied script from props.scriptSrc',
);

const initializeChromecast = window[initializerNamespace];
t.equals(
typeof initializeChromecast,
'function',
'it adds a function into the supplied initializer namespace',
);

let receiverApplicationId;
window.chrome = {
cast: {
media: {
DEFAULT_MEDIA_RECEIVER_APP_ID: 'test-id',
},
},
};
window.cast = {
framework: {
CastContext: {
getInstance() {
return {
setOptions(options) {
receiverApplicationId = options.receiverApplicationId;
},
};
},
},
},
};
initializeChromecast(false);
t.false(
receiverApplicationId,
'it does not initialize chromecast when the sdk signals that the api is unavailable',
);

initializeChromecast(true);
t.equals(
receiverApplicationId,
window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
'it does initialize chromecast with the default receiver when the sdk signals that the api is available',
);

delete global.window;
t.end();
});
1 change: 1 addition & 0 deletions components/ChromecastButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ChromecastButton';
2 changes: 2 additions & 0 deletions components/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Head from 'next/head'
import Link from 'next/link'

import { createShowDate, durationToHHMMSS, removeLeadingZero, splitShowDate } from '../lib/utils'
import ChromecastButton from './ChromecastButton';
import player from '../lib/player'

class Player extends Component {
Expand Down Expand Up @@ -192,6 +193,7 @@ class Player extends Component {
<div><i className="fas fa-forward" onClick={() => player.playNext()} /></div>
<div onClick={this.toggleRemainingDuration}>{durationToHHMMSS(showRemainingDuration ? playback.activeTrack.currentTime - playback.activeTrack.duration : playback.activeTrack.duration)}</div>
</div>
<ChromecastButton />
</div>
<div className="progress-container" onClick={this.onProgressClick} style={{ opacity: playback.activeTrack.currentTime < 0.1 ? 0.8 : null }}>
<div className="progress-background" style={{ width: notchPosition ? notchPosition + 2 : null }} />
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "node server.js",
"build": "next build",
"start": "node server.js",
"deploy": "now -e NODE_ENV=production"
"deploy": "now -e NODE_ENV=production",
"test": "NODE_ENV=test babel-tape-runner ./components/**/*.test.js | faucet"
},
"author": "Daniel Saewitz",
"license": "MIT",
Expand All @@ -30,5 +31,11 @@
"styled-jsx-plugin-stylus": "^0.0.4",
"stylus": "^0.54.5",
"ua-parser-js": "0.7.12"
},
"devDependencies": {
"babel-tape-runner": "^3.0.0",
"faucet": "^0.0.1",
"jsdom": "^12.1.0",
"tape": "^4.9.1"
}
}
12 changes: 12 additions & 0 deletions static/gapless.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,20 @@

play() {
this.debug('play');

if (chrome.cast && window.cast) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff will all be cleared out in the end, ignore for now.

var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

if (castSession) {
var mediaInfo = new chrome.cast.media.MediaInfo(this.trackUrl, 'media');
var request = new chrome.cast.media.LoadRequest(mediaInfo);
castSession.loadMedia(request);
}
}

if (this.audioBuffer) {
// if we've already set up the buffer just set playbackRate to 1

if (this.isUsingWebAudio) {
if (this.bufferSourceNode.playbackRate.value === 1) return;

Expand Down
Loading