diff --git a/app/components/public/session-item.hbs b/app/components/public/session-item.hbs index 925b31b4f74..daf02975099 100644 --- a/app/components/public/session-item.hbs +++ b/app/components/public/session-item.hbs @@ -1,12 +1,20 @@ +{{!-- template-lint-disable no-nested-interactive --}}

{{@session.title}} - {{#unless @hideSessionLink}} - {{!-- template-lint-disable no-nested-interactive --}} - - {{/unless}} + + {{#if @session.microlocation.videoStream}} + + + + {{/if}} + {{#unless @hideSessionLink}} + + {{/unless}} +
{{@session.sessionType.name}}
@@ -81,6 +89,12 @@

{{/if}} + + {{#if @session.microlocation.videoStream.additionalInformation}} +

+ {{@session.microlocation.videoStream.additionalInformation}} +

+ {{/if}} {{#each @session.speakers as |speaker|}} speaker diff --git a/app/components/public/session-item.js b/app/components/public/session-item.js index 9e0928e102e..a2a587c72ed 100644 --- a/app/components/public/session-item.js +++ b/app/components/public/session-item.js @@ -25,4 +25,9 @@ export default class SessionItem extends Component { this.hideImage = false; } } + + @action + goToStream() { + window.open(`/e/${this.args.event?.identifier ?? this.args.session.get('event.identifier')}/stream/${this.args.session.get('microlocation.videoStream.id')}`, '_blank'); + } } diff --git a/app/components/public/stream/video-stream.hbs b/app/components/public/stream/video-stream.hbs new file mode 100644 index 00000000000..2fef4d97c9e --- /dev/null +++ b/app/components/public/stream/video-stream.hbs @@ -0,0 +1,3 @@ +
+ {{@videoStream.name}} +
diff --git a/app/components/public/stream/video-stream.ts b/app/components/public/stream/video-stream.ts new file mode 100644 index 00000000000..f4a584fb44d --- /dev/null +++ b/app/components/public/stream/video-stream.ts @@ -0,0 +1,84 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import VideoStream from 'open-event-frontend/models/video-stream'; +import { getScript } from 'open-event-frontend/utils/loader'; +import AuthManagerService from 'open-event-frontend/services/auth-manager'; +import UrlParser from 'url-parse'; + +declare global { + interface Window { + JitsiMeetExternalAPI: any; + } +} + +interface Args { + videoStream: VideoStream +} + +export default class PublicStreamVideoStream extends Component { + + @service + authManager!: AuthManagerService + + app: HTMLElement | null = null + + getRoomName(parsedUrl: UrlParser): string { + return parsedUrl.pathname.slice(1); // drop leading slash + } + + isJitsi(parsedUrl: UrlParser): boolean { + return parsedUrl.host.endsWith('jit.si'); + } + + @action + async setup(): Promise { + const stream = this.args.videoStream; + + const parsedUrl = new UrlParser(stream.url, true); + + this.app = (document.querySelector('.ember-application') as HTMLElement); + this.app.innerHTML = '
'; + + if (this.isJitsi(parsedUrl)) { + await this.setupJitsi(stream, parsedUrl); + } else { + location.href = stream.url; + } + } + + async setupJitsi(stream: VideoStream, parsedUrl: UrlParser): Promise { + const app = (document.querySelector('.ember-application') as HTMLElement); + app.innerHTML = '
'; + await getScript(parsedUrl.origin + '/external_api.js'); + const domain = parsedUrl.host; + const options = { + roomName : this.getRoomName(parsedUrl), + parentNode : document.querySelector('.ember-application'), + userInfo : { + email : this.authManager.currentUser.email, + displayName : this.authManager.currentUser.fullName + }, + configOverwrite: { + prejoinPageEnabled: false + }, + interfaceConfigOverwrite: { + HIDE_INVITE_MORE_HEADER: true + } + }; + (this.app as HTMLElement).innerHTML = ''; + const api = new window.JitsiMeetExternalAPI(domain, options); + + if (stream.password) { + api.addEventListener('participantRoleChanged', (event: any) => { + if (event.role === 'moderator') { + api.executeCommand('password', stream.password); + } + }); + // join a protected channel + api.on('passwordRequired', () => { + api.executeCommand('password', stream.password); + }); + } + } +} diff --git a/app/controllers/events/list.js b/app/controllers/events/list.js index 0bf9985e710..7f1101c1802 100644 --- a/app/controllers/events/list.js +++ b/app/controllers/events/list.js @@ -6,7 +6,7 @@ export default class extends Controller.extend(EmberTableControllerMixin) { sort_by = 'starts-at'; sort_dir = 'DSC'; - + get columns() { return [ { diff --git a/app/router.js b/app/router.js index 544ee8e9205..3203ccb1ccd 100644 --- a/app/router.js +++ b/app/router.js @@ -45,6 +45,9 @@ Router.map(function() { this.route('session', function() { this.route('view', { path: '/:session_id' }); }); + this.route('stream', function() { + this.route('view', { path: '/:stream_id' }); + }); this.route('speaker', function() { this.route('view', { path: '/:speaker_id' }); }); diff --git a/app/routes/public/sessions.js b/app/routes/public/sessions.js index f10d16c8d8d..dac5ad6c330 100644 --- a/app/routes/public/sessions.js +++ b/app/routes/public/sessions.js @@ -28,15 +28,6 @@ export default class SessionsRoute extends Route { const filterOptions = [ { and: [ - { - name : 'event', - op : 'has', - val : { - name : 'identifier', - op : 'eq', - val : eventDetails.id - } - }, { or: [ { @@ -98,14 +89,15 @@ export default class SessionsRoute extends Route { return { event : eventDetails, - session : await this.infinity.model('session', { - include : 'track,speakers,session-type,microlocation', + session : await this.infinity.model('sessions', { + include : 'track,speakers,session-type,microlocation.video-stream', filter : filterOptions, sort : params.sort || 'starts-at', perPage : 6, startingPage : 1, perPageParam : 'page[size]', - pageParam : 'page[number]' + pageParam : 'page[number]', + store : eventDetails }) }; } diff --git a/app/routes/public/stream/view.ts b/app/routes/public/stream/view.ts new file mode 100644 index 00000000000..cf80dc8a759 --- /dev/null +++ b/app/routes/public/stream/view.ts @@ -0,0 +1,12 @@ +import Route from '@ember/routing/route'; +import VideoStream from 'open-event-frontend/models/video-stream'; + +export default class PublicStreamView extends Route { + titleToken(model: VideoStream): string { + return model.name; + } + + model(params: { stream_id: number }): VideoStream { + return this.store.findRecord('video-stream', params.stream_id); + } +} diff --git a/app/templates/public/stream/view.hbs b/app/templates/public/stream/view.hbs new file mode 100644 index 00000000000..f541cbc0781 --- /dev/null +++ b/app/templates/public/stream/view.hbs @@ -0,0 +1 @@ + diff --git a/tests/unit/routes/public/stream/view-test.ts b/tests/unit/routes/public/stream/view-test.ts new file mode 100644 index 00000000000..68f8e10084b --- /dev/null +++ b/tests/unit/routes/public/stream/view-test.ts @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | public/stream/view', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + const route = this.owner.lookup('route:public/stream/view'); + assert.ok(route); + }); +});