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

[NEW] Youtube Broadcasting #10127

Merged
merged 60 commits into from
Jun 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2b190f2
POC - YT oauth
gdelavald Feb 20, 2018
ff69f69
youtube auth
gdelavald Mar 2, 2018
4e2e1e2
Merge remote-tracking branch 'origin/develop' into youtube-broadcast
ggazzo Mar 7, 2018
3c2830e
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Mar 7, 2018
cfef679
livestream from broadcast tab
gdelavald Mar 7, 2018
3b09839
Merge branch 'youtube-broadcast' of github.com:RocketChat/Rocket.Chat…
ggazzo Mar 7, 2018
21f9932
almos done
ggazzo Mar 8, 2018
c78df8a
Kill ffmpeg process
gdelavald Mar 8, 2018
faa372b
change methods
ggazzo Mar 8, 2018
50ee736
refactor
ggazzo Mar 8, 2018
74698f5
undo problems
ggazzo Mar 8, 2018
ca1a858
refactoring some logs
gdelavald Mar 8, 2018
7968d7a
Merge branch 'youtube-broadcast' of https://github.com/RocketChat/Roc…
gdelavald Mar 8, 2018
6bc9f0d
tests
ggazzo Mar 8, 2018
1450107
Merge branch 'youtube-broadcast' of github.com:RocketChat/Rocket.Chat…
ggazzo Mar 8, 2018
b32e214
Customize popout for streaming
gdelavald Mar 8, 2018
0337941
actions buttons
ggazzo Mar 12, 2018
af2263b
stop media on destroy popout
gdelavald Mar 12, 2018
6c7dfb3
Add transition steps
gdelavald Mar 12, 2018
a6f58a2
remove logs
gdelavald Mar 12, 2018
b86df53
fix errors
gdelavald Mar 12, 2018
5d7dd43
fix
ggazzo Mar 13, 2018
05a3354
Finishing UI and improvments to broadcast flow
gdelavald Mar 13, 2018
eebe4a5
Finishing UId improvments to broadcast flow
gdelavald Mar 13, 2018
96d59cc
lint fix
gdelavald Mar 13, 2018
bb728a0
Merge branch 'youtube-broadcast' of https://github.com/RocketChat/Roc…
gdelavald Mar 13, 2018
29acd17
update localhost urls for auth
gdelavald Mar 13, 2018
a67e449
update site url
gdelavald Mar 13, 2018
8e70694
remove encoder and add media server url settings
gdelavald Mar 14, 2018
d7bd7df
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Mar 15, 2018
f606fec
Adds livestream notifier and clears tab when closing broadcast
gdelavald Mar 26, 2018
4f8fdef
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Apr 3, 2018
0003526
Adds configurable message when livestream is not available
gdelavald Apr 4, 2018
1471263
Fix classes starting with -- and remove npm folder
gdelavald Apr 6, 2018
0bbdbd9
Adds basic API for RoomAnnouncement
gdelavald Apr 9, 2018
447d0c3
Adds Announcement to default lib
gdelavald Apr 10, 2018
e3bc802
Adds style
gdelavald Apr 10, 2018
f90e85f
Adds announcement styles and colors for warning/error
gdelavald Apr 10, 2018
debb021
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Apr 11, 2018
8ba5dbd
Merge branch 'room-announcements' of https://github.com/RocketChat/Ro…
gdelavald Apr 11, 2018
b50f25f
Adds announcement when starting broadcast on room
gdelavald Apr 11, 2018
82f7dd8
fixing lint errors
gdelavald Apr 11, 2018
52d5d36
Remove unused deps and console.log
gdelavald Apr 11, 2018
31ab23d
Merge branch 'develop' into youtube-broadcast
karlprieb Apr 11, 2018
b19df38
Merge branch 'develop' into youtube-broadcast
gdelavald Apr 19, 2018
23f1a7e
Fixing deps (maybe)
gdelavald Apr 19, 2018
9375aab
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Apr 23, 2018
eddcb66
update package-lock
gdelavald Apr 23, 2018
1b17e42
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald Apr 23, 2018
911cd26
Fix gettting room announcement and route for livestream oauth callback
gdelavald Apr 23, 2018
3a2391d
Save audio only option when clearing details
gdelavald Apr 23, 2018
c7c2306
Fix broken callback url
gdelavald Apr 23, 2018
af3bb99
Reorder livestream icon
gdelavald Apr 30, 2018
5bf1917
Syncing from develop
gdelavald Apr 30, 2018
4e26e71
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
gdelavald May 11, 2018
a5c525a
Adds check to display broadcast camera button
gdelavald May 11, 2018
477b6d1
Merge remote-tracking branch 'origin/develop' into youtube-broadcast
ggazzo Jun 13, 2018
c51709d
Merge remote-tracking branch 'origin/youtube-broadcast' into youtube-…
ggazzo Jun 13, 2018
192903e
livestream order
ggazzo Jun 13, 2018
cf7af91
Merge branch 'develop' into youtube-broadcast
ggazzo Jun 15, 2018
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
15 changes: 15 additions & 0 deletions packages/rocketchat-lib/client/lib/RocketChatAnnouncement.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,19 @@ export class RocketChatAnnouncement {
getStyle() {
return this.callback.get();
}
getByRoom(rid) {
const roomData = Session.get(`roomData${ rid }`);
if (!roomData) { return null; }
this.room.set(rid);
this.message.set(roomData.announcement);
this.callback.set(roomData.announcementDetails ? roomData.announcementDetails.callback : null);
this.style.set(roomData.announcementDetails ? roomData.announcementDetails.style : null);
return this;
}
clear() {
this.message.set(null);
this.callback.set(null);
this.style.set(null);
Meteor.call('saveRoomSettings', this.room.get(), 'roomAnnouncement', {});
}
}
8 changes: 7 additions & 1 deletion packages/rocketchat-lib/client/lib/TabBar.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import _ from 'underscore';

RocketChat.TabBar = new (class TabBar {
get size() {
return this._size.get();
}
set size(s) {
this._size.set(s);
}
constructor() {
this.buttons = new ReactiveVar({});

this._size = new ReactiveVar(4);
this.extraGroups = {};
}

Expand Down
1 change: 1 addition & 0 deletions packages/rocketchat-livestream/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.npm/
15 changes: 15 additions & 0 deletions packages/rocketchat-livestream/client/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const close = (popup) => {
return new Promise(function(resolve) {
const checkInterval = setInterval(() => {
if (popup.closed) {
clearInterval(checkInterval);
resolve();
}
}, 300);
});
};

export const auth = async() => {
const oauthWindow = window.open(`${ RocketChat.settings.get('Site_Url') }/api/v1/livestream/oauth?userId=${ Meteor.userId() }`, 'youtube-integration-oauth', 'width=400,height=600');
return await close(oauthWindow);
};
26 changes: 17 additions & 9 deletions packages/rocketchat-livestream/client/styles/liveStreamTab.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@
&.livestream {
justify-content: flex-start;
}
& .rc-button--stack {
width: 100%;
}
}

.thumbnail-container {
position: relative;
height: 270px;

& .--absolute-center {
position: absolute;
top: calc(50% - 25px);
left: calc(50% - 25px);
& .popout {
&--absolute-center {
position: absolute;
top: calc(50% - 25px);
left: calc(50% - 25px);

font-size: 50px;
}
& .--play-solid {
fill: var(--rc-color-primary-lightest);;
font-size: 50px;
}
&--play-solid {
fill: white;
}
}
}
.liveStreamTab__form {
& .rc-switch {
padding: 15px 5px;
}
& .rc-input__label p {
padding: 10px 0;
}
}

.livestream--url {
Expand All @@ -37,7 +45,7 @@
& .current-setting {
display: inline-block;
text-decoration: none;
color: var(--rc-color-primary-dark);
color: black;
}

& .rc-button {
Expand Down
11 changes: 8 additions & 3 deletions packages/rocketchat-livestream/client/tabBar.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
Meteor.startup(function() {
Tracker.autorun(function() {
RocketChat.TabBar.removeButton('livestream');
if (RocketChat.settings.get('Livestream_enabled')) {
const live = RocketChat.models.Rooms.findOne({ _id: Session.get('openedRoom'), 'streamingOptions.id': {$exists :1} }, { fields: { streamingOptions: 1 } });
RocketChat.TabBar.size = live ? 5 : 4;
return RocketChat.TabBar.addButton({
groups: ['channel', 'group'],
id: 'livestream',
i18nTitle: 'Livestream',
icon: 'podcast',
template: 'liveStreamTab',
order: 3
order: live ? -1 : 15,
class: () => {
const roomWithStream = RocketChat.models.Rooms.findOne({ _id: Session.get('openedRoom'), 'streamingOptions.id': {$exists :1} }, { fields: { streamingOptions: 1 } }) || '';
return roomWithStream && 'live';
}
});
} else {
RocketChat.TabBar.removeButton('livestream');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template name="broadcastView">
<div class="streaming-popup">
<video id="player" src={{ broadcastSource }} muted autoplay width="380px" ></video>
</div>
</template>
150 changes: 150 additions & 0 deletions packages/rocketchat-livestream/client/views/broadcastView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const getMedia = () => navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
const createAndConnect = (url) => {
if (!'WebSocket' in window) { // eslint-disable-line no-negated-in-lhs
return false;
}

const ws = new WebSocket(url);
ws.onerror = (evt) => console.error(`Error: ${ evt.data }`);
return ws;
};
const sendMessageToWebSocket = (message, ws) => {
if (ws != null) {
if (ws.readyState === 1) { ws.send(message); }
}
};
export const call = (...args) => new Promise(function(resolve, reject) {
Copy link
Member

Choose a reason for hiding this comment

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

❤️

Meteor.call(...args, function(err, result) {
if (err) {
handleError(err);
reject(err);
}
resolve(result);
});
});

const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));

const waitForStreamStatus = async(id, status) => {
const streamActive = new Promise(async(resolve) => {
while (true) { //eslint-disable-line no-constant-condition
Copy link
Member

Choose a reason for hiding this comment

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

<3

const currentStatus = await call('livestreamStreamStatus', { streamId: id });
if (currentStatus === status) {
return resolve(status);
}
await delay(1500);
}
});
await streamActive;
};
const waitForBroadcastStatus = async(id, status) => {
const broadcastActive = new Promise(async(resolve) => {
while (true) { //eslint-disable-line no-constant-condition
const currentStatus = await call('getBroadcastStatus', { broadcastId: id });
if (currentStatus === status) {
return resolve(status);
}
await delay(1500);
}
});
await broadcastActive;
};

Template.broadcastView.helpers({
broadcastSource() {
return Template.instance().mediaStream.get() ? window.URL.createObjectURL(Template.instance().mediaStream.get()) : '';
},
mediaRecorder() {
Template.instance().mediaRecorder.get();
}
});

Template.broadcastView.onCreated(async function() {
const connection = createAndConnect(`${ RocketChat.settings.get('Broadcasting_media_server_url') }/${ this.data.id }`);
this.mediaStream = new ReactiveVar(null);
this.mediaRecorder = new ReactiveVar(null);
this.connection = new ReactiveVar(connection);

if (!connection) {
return;
}
});
Template.broadcastView.onDestroyed(function() {
if (this.connection.get()) {
this.connection.get().close();
}
if (this.mediaRecorder.get()) {
this.mediaRecorder.get().stop();
this.mediaRecorder.set(null);
}
if (this.mediaStream.get()) {
this.mediaStream.get().getTracks().map((track) => track.stop());
this.mediaStream.set(null);
}
});
Template.broadcastView.onRendered(async function() {
navigator.getMedia = getMedia();
if (!navigator.getMedia) {
return alert('getUserMedia() is not supported in your browser!');
}
const localMediaStream = await new Promise((resolve, reject) => navigator.getMedia({video: true, audio: true}, resolve, reject));

const connection = this.connection.get();

this.mediaStream.set(localMediaStream);
let options = {mimeType: 'video/webm;codecs=vp9'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = {mimeType: 'video/webm;codecs=vp8'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = {mimeType: 'video/webm'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = {mimeType: ''};
}
}
}
try {
const mediaRecorder = new MediaRecorder(localMediaStream, options);
mediaRecorder.ondataavailable = (event) => {
if (!(event.data || event.data.size > 0)) {
return;
}
sendMessageToWebSocket(event.data, connection);
};
mediaRecorder.start(100); // collect 100ms of data
this.mediaRecorder.set(mediaRecorder);

await waitForStreamStatus(this.data.stream.id, 'active');
await call('setLivestreamStatus', { broadcastId: this.data.broadcast.id, status: 'testing' });
await waitForBroadcastStatus(this.data.broadcast.id, 'testing');
document.querySelector('.streaming-popup').dispatchEvent(new Event('broadcastStreamReady'));

} catch (e) {
console.log(e);
}
});

Template.broadcastView.events({
async 'startStreaming .streaming-popup'(e, i) {
await call('setLivestreamStatus', {broadcastId: i.data.broadcast.id, status: 'live'});
document.querySelector('.streaming-popup').dispatchEvent(new Event('broadcastStream'));
await call('saveRoomSettings', Session.get('openedRoom'), 'streamingOptions', {id: i.data.broadcast.id, url: `https://www.youtube.com/embed/${ i.data.broadcast.id }`, thumbnail: `https://img.youtube.com/vi/${ i.data.broadcast.id }/0.jpg`});
},
async 'stopStreaming .streaming-popup'(e, i) {
await call('setBroadcastStatus', { broadcastId: i.data.broadcast.id, status: 'complete' });
await call('saveRoomSettings', Session.get('openedRoom'), 'streamingOptions', {}, (err) => {
if (err) {
return handleError(err);
}
i.editing.set(false);
i.streamingOptions.set({});
});
if (i.mediaRecorder.get()) {
i.mediaRecorder.get().stop();
i.mediaRecorder.set(null);
}
if (i.mediaStream.get()) {
i.mediaStream.get().getTracks().map((track) => track.stop());
i.mediaStream.set(null);
}
}
});
25 changes: 18 additions & 7 deletions packages/rocketchat-livestream/client/views/liveStreamTab.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,33 @@
{{else}}
{{#if hasThumbnail}}
<div class="thumbnail-container" style="background: url({{thumbnailUrl}}) 100% no-repeat no-repeat;">
<a href="#" class="js-popout --absolute-center">
{{> icon icon="play-solid"}}
<a href="#" class="js-popout popout--absolute-center">
{{> icon icon="play-solid" block="popout"}}
</a>
</div>
{{else}}
<a href="#" class="js-popout --absolute-center">
{{> icon icon="play-solid"}}
<a href="#" class="js-popout popout--absolute-center">
{{> icon icon="play-solid" block="popout"}}
</a>
{{/if}}
{{/if}}
{{else}}
<p> {{_ "Livestream_not_found" }} </p>
<p> {{ streamingUnavailableMessage }} </p>
{{/if}}
{{#if canEdit}}
{{#if editing}}
{{#if broadcastEnabled}}
{{ > liveStreamBroadcast}}
{{/if}}
<form class="liveStreamTab__form">
<input type="text" name="streaming-source" class="rc-input__element content-background-color editing" placeholder="{{_ "Livestream_url"}}" value="{{ streamingSource }}"/>
<label class="rc-input__label">
<p> Livestream not available message: </p>
<input type="text" name="streaming-message" class="rc-input__element content-background-color editing" placeholder="{{_ "Livestream_not_found"}}" value="{{ streamingUnavailableMessage }}"/>
</label>
<label class="rc-input__label">
<p> Livestream source (URL): </p>
<input type="text" name="streaming-source" class="rc-input__element content-background-color editing" placeholder="{{_ "Livestream_url"}}" value="{{ streamingSource }}"/>
</label>
<div class="rc-switch">
<label class="rc-switch__label" tabindex="-1">
<input type="checkbox" class="rc-switch__input" name="streaming-audio-only" checked={{isAudioOnly}}>
Expand All @@ -40,7 +50,8 @@
</div>
<div class="rc-user-info__flex">
<button type="button" class="rc-button rc-button--nude js-cancel button-block" title="{{_ 'Cancel'}}" >{{_ "Cancel"}}</button>
<button type="button" class="rc-button rc-button--primary js-save button-block">{{_ "Save"}}</button>
<button type="button" class="rc-button rc-button--submit js-clear button-block" title="{{_ 'Clear'}}" >{{_ "Clear"}}</button>
<button type="button" class="rc-button rc-button--primary js-save button-block" title="{{_ 'Save'}}">{{_ "Save"}}</button>
</div>
</form>
{{else}}
Expand Down
Loading