diff --git a/addons/website_event_track_live/__init__.py b/addons/website_event_track_live/__init__.py
index 67dee8c60db..7d34c7c054a 100644
--- a/addons/website_event_track_live/__init__.py
+++ b/addons/website_event_track_live/__init__.py
@@ -1,2 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import controllers
+from . import models
diff --git a/addons/website_event_track_live/__manifest__.py b/addons/website_event_track_live/__manifest__.py
index 98e2c69bc9a..8f137e270e2 100644
--- a/addons/website_event_track_live/__manifest__.py
+++ b/addons/website_event_track_live/__manifest__.py
@@ -15,8 +15,16 @@
'website_event_track_session',
],
'data': [
+ 'views/assets.xml',
+ 'views/event_track_templates_list.xml',
+ 'views/event_track_templates_page.xml',
+ 'views/event_track_views.xml',
],
'demo': [
+ 'data/event_track_demo.xml'
+ ],
+ 'qweb': [
+ 'static/src/xml/website_event_track_live_templates.xml'
],
'application': False,
'installable': True,
diff --git a/addons/website_event_track_live/controllers/__init__.py b/addons/website_event_track_live/controllers/__init__.py
new file mode 100644
index 00000000000..df58fe9572a
--- /dev/null
+++ b/addons/website_event_track_live/controllers/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import track_live
+from . import session
diff --git a/addons/website_event_track_live/controllers/session.py b/addons/website_event_track_live/controllers/session.py
new file mode 100644
index 00000000000..9d6381e6300
--- /dev/null
+++ b/addons/website_event_track_live/controllers/session.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import re
+
+from odoo.addons.website_event_track_session.controllers.session import WebsiteEventSessionController
+from odoo.http import request
+
+
+class WebsiteEventSessionLiveController(WebsiteEventSessionController):
+
+ def _event_track_page_get_values(self, event, track, **options):
+ if 'widescreen' not in options:
+ options['widescreen'] = bool(track.youtube_video_url)
+ values = super(WebsiteEventSessionLiveController, self)._event_track_page_get_values(event, track, **options)
+ # Youtube disables the chat embed on all mobile devices
+ # This regex is a naive attempt at matching their behavior (should work for most cases)
+ values['is_mobile_chat_disabled'] = bool(re.match(
+ r'^.*(Android|iPad|iPhone).*',
+ request.httprequest.headers.get('User-Agent', request.httprequest.headers.get('user-agent', ''))))
+ return values
diff --git a/addons/website_event_track_live/controllers/track_live.py b/addons/website_event_track_live/controllers/track_live.py
new file mode 100644
index 00000000000..d85d3d13c79
--- /dev/null
+++ b/addons/website_event_track_live/controllers/track_live.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import http
+
+from odoo.addons.website_event_track_online.controllers.event_track import EventTrackOnlineController
+
+
+class EventTrackLiveController(EventTrackOnlineController):
+
+ @http.route('/event_track/get_track_suggestion', type='json', auth='public', website=True)
+ def get_next_track_suggestion(self, track_id):
+ track = self._fetch_track(track_id)
+ track_suggestion = track._get_track_suggestions(
+ restrict_domain=[('youtube_video_url', '!=', False), ('is_published', '=', True)],
+ limit=1)
+ if not track_suggestion:
+ return False
+ track_suggestion_sudo = track_suggestion.sudo()
+ track_sudo = track.sudo()
+ return self._prepare_track_suggestion_values(track_sudo, track_suggestion_sudo)
+
+ def _prepare_track_suggestion_values(self, track, track_suggestion):
+ return {
+ 'current_track': {
+ 'name': track.name,
+ 'website_image_url': track.website_image_url,
+ },
+ 'suggestion': {
+ 'id': track_suggestion.id,
+ 'name': track_suggestion.name,
+ 'speaker_name': track_suggestion.partner_name,
+ 'website_url': track_suggestion.website_url
+ }
+ }
diff --git a/addons/website_event_track_live/data/event_track_demo.xml b/addons/website_event_track_live/data/event_track_demo.xml
new file mode 100644
index 00000000000..79e01e8c5f0
--- /dev/null
+++ b/addons/website_event_track_live/data/event_track_demo.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ https://www.youtube.com/watch?v=66AIsiezq5g
+
+
+
+ https://www.youtube.com/watch?v=IvUU8joBb1Q
+
+
+
+ https://www.youtube.com/watch?v=qRrA8al77a0
+
+
+
+ https://www.youtube.com/watch?v=bZ-queeu-lk
+
+
+
+ https://www.youtube.com/watch?v=gV3g5FzmQiM
+
+
+
+ https://www.youtube.com/watch?v=g3IoL9hxABg
+
+
+
+
+ https://www.youtube.com/watch?v=AVZtyXJB-rI
+
+
+
+ https://www.youtube.com/watch?v=LHztthKy4RE
+
+
+
+ https://www.youtube.com/watch?v=B-nEYsyRlYo
+
+
+
+ https://www.youtube.com/watch?v=7X8xuipr4_A
+
+
+
+
diff --git a/addons/website_event_track_live/models/__init__.py b/addons/website_event_track_live/models/__init__.py
new file mode 100644
index 00000000000..e3798014924
--- /dev/null
+++ b/addons/website_event_track_live/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import event_track
diff --git a/addons/website_event_track_live/models/event_track.py b/addons/website_event_track_live/models/event_track.py
new file mode 100644
index 00000000000..7765bfa455b
--- /dev/null
+++ b/addons/website_event_track_live/models/event_track.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import re
+
+from odoo import api, fields, models
+
+
+class Track(models.Model):
+ _inherit = 'event.track'
+
+ youtube_video_url = fields.Char('Youtube Video URL',
+ help="Configure this URL so that event attendees can see your Track in video!")
+ youtube_video_id = fields.Char('Youtube video ID', compute='_compute_youtube_video_id',
+ help="Extracted from the video URL and used to infer various links (embed/thumbnail/...)")
+ is_youtube_replay = fields.Boolean('Is Youtube Replay',
+ help="Check this option if the video is already available on Youtube to avoid showing 'Direct' options (Chat, ...)")
+ is_youtube_chat_available = fields.Boolean('Is Chat Available', compute='_compute_is_youtube_chat_available')
+
+ @api.depends('youtube_video_url')
+ def _compute_youtube_video_id(self):
+ for track in self:
+ if track.youtube_video_url:
+ regex = r'^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*'
+ match = re.match(regex, track.youtube_video_url)
+ if match and len(match.groups()) == 2 and len(match.group(2)) == 11:
+ track.youtube_video_id = match.group(2)
+
+ if not track.youtube_video_id:
+ track.youtube_video_id = False
+
+ @api.depends('youtube_video_id', 'is_youtube_replay', 'date_end', 'is_track_done')
+ def _compute_website_image_url(self):
+ youtube_thumbnail_tracks = self.filtered(lambda track: not track.website_image and track.youtube_video_id)
+ super(Track, self - youtube_thumbnail_tracks)._compute_website_image_url()
+ for track in youtube_thumbnail_tracks:
+ track.website_image_url = f'https://img.youtube.com/vi/{track.youtube_video_id}/maxresdefault.jpg'
+
+ @api.depends('youtube_video_url', 'is_youtube_replay', 'date', 'date_end', 'is_track_upcoming', 'is_track_live')
+ def _compute_is_youtube_chat_available(self):
+ for track in self:
+ track.is_youtube_chat_available = track.youtube_video_url and not track.is_youtube_replay and (track.is_track_soon or track.is_track_live)
diff --git a/addons/website_event_track_live/static/src/js/website_event_track_live.js b/addons/website_event_track_live/static/src/js/website_event_track_live.js
new file mode 100644
index 00000000000..a330308de63
--- /dev/null
+++ b/addons/website_event_track_live/static/src/js/website_event_track_live.js
@@ -0,0 +1,121 @@
+var onYouTubeIframeAPIReady;
+
+odoo.define('website_event_track_live.website_event_youtube_embed', function (require) {
+'use strict';
+
+var publicWidget = require('web.public.widget');
+var TrackSuggestionWidget = require('website_event_track_live.website_event_track_suggestion');
+
+var YOUTUBE_VIDEO_ENDED = 0;
+var YOUTUBE_VIDEO_PLAYING = 1;
+var YOUTUBE_VIDEO_PAUSED = 2;
+
+publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
+ selector: '.o_wevent_event_track_live',
+ custom_events: _.extend({}, publicWidget.Widget.prototype.custom_events, {
+ 'video-ended': '_onVideoEnded'
+ }),
+
+ start: function () {
+ var self = this;
+ return this._super(...arguments).then(function () {
+ self._setupYoutubePlayer();
+ self.isFullScreen = !!document.fullscreenElement;
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ _onPlayerReady: function () {
+ $(window).on('resize', this._onResize.bind(this));
+ this.$('.o_wevent_event_track_live_loading').remove();
+ },
+
+ _onPlayerStateChange: function (event) {
+ if (event.data === YOUTUBE_VIDEO_ENDED) {
+ this.trigger('video-ended');
+ } else if (event.data === YOUTUBE_VIDEO_PLAYING) {
+ this.trigger('video-playing');
+ } else if (event.data === YOUTUBE_VIDEO_PAUSED) {
+ this.trigger('video-paused');
+ }
+ },
+
+ _onVideoEnded: function () {
+ var self = this;
+ this._rpc({
+ route: '/event_track/get_track_suggestion',
+ params: {
+ track_id: this.$el.data('trackId'),
+ }
+ }).then(function (suggestion) {
+ self.nextSuggestion = suggestion;
+ self._showSuggestion();
+ });
+ },
+
+ _onReplay: function () {
+ this.youtubePlayer.seekTo(0);
+ this.youtubePlayer.playVideo();
+ if (this.trackSuggestion) {
+ delete this.trackSuggestion;
+ }
+ },
+
+ /**
+ * The 'fullscreenchange' event is probably a better fit but it is unfortunately
+ * not triggered when Youtube enters fullscreen mode.
+ * However, the global window 'resize' is.
+ */
+ _onResize: function () {
+ this.isFullScreen = !!document.fullscreenElement;
+ this._showSuggestion();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _setupYoutubePlayer: function () {
+ var self = this;
+
+ var youtubeId = self.$el.data('youtubeVideoId');
+ var $youtubeElement = $('', {src: 'https://www.youtube.com/iframe_api'});
+ $(document.head).append($youtubeElement);
+
+ onYouTubeIframeAPIReady = function () {
+ self.youtubePlayer = new YT.Player('o_wevent_youtube_iframe_container', {
+ height: '100%',
+ width: '100%',
+ videoId: youtubeId,
+ playerVars: {
+ autoplay: 1,
+ enablejsapi: 1,
+ rel: 0,
+ origin: window.location.origin,
+ widget_referrer: window.location.origin,
+ },
+ events: {
+ 'onReady': self._onPlayerReady.bind(self),
+ 'onStateChange': self._onPlayerStateChange.bind(self)
+ }
+ });
+ };
+ },
+
+ /**
+ * Automatically launches the next suggested track when this one ends
+ * and the user is not in fullscreen mode.
+ */
+ _showSuggestion: function () {
+ if (this.nextSuggestion && !this.isFullScreen && !this.trackSuggestion) {
+ this.trackSuggestion = new TrackSuggestionWidget(this, this.nextSuggestion);
+ this.trackSuggestion.appendTo(this.$el);
+ this.trackSuggestion.on('replay', null, this._onReplay.bind(this));
+ }
+ }
+});
+
+});
diff --git a/addons/website_event_track_live/static/src/js/website_event_track_suggestion.js b/addons/website_event_track_live/static/src/js/website_event_track_suggestion.js
new file mode 100644
index 00000000000..e6583c22509
--- /dev/null
+++ b/addons/website_event_track_live/static/src/js/website_event_track_suggestion.js
@@ -0,0 +1,81 @@
+odoo.define('website_event_track_live.website_event_track_suggestion', function (require) {
+'use strict';
+
+var Widget = require('web.Widget');
+
+var WebsiteEventTrackSuggestion = Widget.extend({
+ template: 'website_event_track_live.website_event_track_suggestion',
+ xmlDependencies: ['/website_event_track_live/static/src/xml/website_event_track_live_templates.xml'],
+ events: {
+ 'click .owevent_track_suggestion_next': '_onNextTrackClick',
+ 'click .owevent_track_suggestion_close': '_onCloseClick',
+ 'click .owevent_track_suggestion_replay': '_onReplayClick'
+ },
+
+ init: function (parent, options) {
+ this._super(...arguments);
+
+ this.currentTrack = {
+ 'name': options.current_track.name,
+ 'imageSrc': options.current_track.website_image_url,
+ };
+ this.suggestion = {
+ 'name': options.suggestion.name,
+ 'speakerName': options.suggestion.speaker_name,
+ 'trackUrl': options.suggestion.website_url,
+ };
+ },
+
+ start: function () {
+ var self = this;
+ this._super(...arguments).then(function () {
+ self.timerInterval = setInterval(self._updateTimer.bind(self), 1000);
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * If the user clicks on replay, remove this suggestion window and send an
+ * event to the parent so that it can rewind the video to the beginning.
+ */
+ _onReplayClick: function () {
+ this.trigger_up('replay');
+ clearInterval(this.timerInterval);
+ this.destroy();
+ },
+
+ _onCloseClick: function () {
+ clearInterval(this.timerInterval);
+ this.$('.owevent_track_suggestion_next').addClass('invisible');
+ },
+
+ _onNextTrackClick: function (ev) {
+ if ($(ev.target).hasClass('owevent_track_suggestion_close')) {
+ return;
+ }
+
+ window.location = this.suggestion.trackUrl;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _updateTimer: function () {
+ var secondsLeft = parseInt(this.$('.owevent_track_suggestion_timer_text').text());
+
+ if (secondsLeft > 1) {
+ secondsLeft -= 1;
+ this.$('.owevent_track_suggestion_timer_text').text(secondsLeft);
+ } else {
+ window.location = this.suggestion.trackUrl;
+ }
+ }
+});
+
+return WebsiteEventTrackSuggestion;
+
+});
diff --git a/addons/website_event_track_live/static/src/scss/website_event_track_live.scss b/addons/website_event_track_live/static/src/scss/website_event_track_live.scss
new file mode 100644
index 00000000000..28571e01ffa
--- /dev/null
+++ b/addons/website_event_track_live/static/src/scss/website_event_track_live.scss
@@ -0,0 +1,42 @@
+.o_wesession_track_aside_tab_chat {
+ height: 70vh;
+}
+
+#o_wevent_youtube_iframe_container, .o_wevent_event_track_live_loading {
+ height: 85vh;
+}
+
+.o_wevent_event_track_live_loading {
+ background-color: #888888;
+
+ i {
+ bottom: 1px;
+ }
+}
+
+.owevent_track_suggestion {
+ background-color: #888888;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ height: 85vh;
+ top: 0px;
+ left: 0px;
+
+ .owevent_track_suggestion_content {
+ background-color: rgba(0,0,0,.5);
+
+ .owevent_track_suggestion_next {
+ background-color: rgba(0,0,0,.5);
+ transition: background-color .3s linear;
+ &:hover {
+ cursor: pointer;
+ background-color: rgba(0,0,0,.9);
+ }
+
+ .owevent_track_suggestion_close {
+ top: .5rem;
+ right: .5rem;
+ }
+ }
+ }
+}
diff --git a/addons/website_event_track_live/static/src/xml/website_event_track_live_templates.xml b/addons/website_event_track_live/static/src/xml/website_event_track_live_templates.xml
new file mode 100644
index 00000000000..1784afae39c
--- /dev/null
+++ b/addons/website_event_track_live/static/src/xml/website_event_track_live_templates.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
You just watched:
+
+
+
+
+
+
+
+
+
Up Next:
+
+
+ Starts in
+ 30
+
+
+
+
+
+
+
diff --git a/addons/website_event_track_live/views/assets.xml b/addons/website_event_track_live/views/assets.xml
new file mode 100644
index 00000000000..0e7e85ce5b7
--- /dev/null
+++ b/addons/website_event_track_live/views/assets.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_event_track_live/views/event_track_templates_list.xml b/addons/website_event_track_live/views/event_track_templates_list.xml
new file mode 100644
index 00000000000..b2fc5243af7
--- /dev/null
+++ b/addons/website_event_track_live/views/event_track_templates_list.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Replay
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_event_track_live/views/event_track_templates_page.xml b/addons/website_event_track_live/views/event_track_templates_page.xml
new file mode 100644
index 00000000000..f72152f6e71
--- /dev/null
+++ b/addons/website_event_track_live/views/event_track_templates_page.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ track.track_start_remaining and not track.is_youtube_replay
+
+
+
+
+
+
+
+ Loading Video...
+
+
+
+
+
+
+
+
+
+
+
+ Chat
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_event_track_live/views/event_track_views.xml b/addons/website_event_track_live/views/event_track_views.xml
new file mode 100644
index 00000000000..9ac764c79f8
--- /dev/null
+++ b/addons/website_event_track_live/views/event_track_views.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ event.track.view.form.inherit.live
+ event.track
+
+
+
+
+
+
+
+
+
+