From fab82c73a5aeb31a3be970dfe31ac754597dbb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Warnon?= Date: Tue, 30 Jun 2020 12:29:03 +0000 Subject: [PATCH] [ADD] website_event_track_live: configure Youtube videos / streams on event.tracks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RATIONALE Events are sometimes held online, gathering a community. In this merge we improve Event application to better support full-online events with improved tracks, wishlists, chat rooms, ... PURPOSE Allow to include a youtube video / live stream on event tracks and update the interface to show "online" capabilities. SPECIFICATIONS This commit improves the existing event.tracks to include the possibility to configure a Youtube live stream or video. The frontend view of the track was adapted accordingly to show the stream / video instead of the track description when available. At the end of the video, the track view will automatically suggest a next track to the attendee in order to keep him looping through interesting tracks. Those suggestions are based on an algorithm that tries to select the track that would be the most interesting to this attendee based on his wishlist, similar tags with current track, same room, ... In addition, we automatically fallback to a Youtube thumbnail for tracks website image when available. We support two youtube modes * streaming: start and end of video is detected based on youtube API; * replay: boolean flag on track, telling the video is a static one, allowing to know by advance the end of video, and to pre-compute thumbnail and give early access if necessary; KNOWN LIMITATIONS Tracks do not hold any protection for payment-based tickets. Everything that is published is available using standard ACLs. Content protection based on tickets is currently not supported. LINKS Task ID-2252655 (Main Online Event task) Task ID-2283735 (Talks -> Session upgrade) Task ID-2283729 (Live/Youtube Tracks) Co-Authored-By: Aurélien Warnon Co-Authored-By: David Beguin Co-Authored-By: Thibault Delavallée --- addons/website_event_track_live/__init__.py | 3 + .../website_event_track_live/__manifest__.py | 8 ++ .../controllers/__init__.py | 5 + .../controllers/session.py | 21 +++ .../controllers/track_live.py | 35 +++++ .../data/event_track_demo.xml | 46 +++++++ .../models/__init__.py | 4 + .../models/event_track.py | 42 ++++++ .../static/src/js/website_event_track_live.js | 121 ++++++++++++++++++ .../src/js/website_event_track_suggestion.js | 81 ++++++++++++ .../src/scss/website_event_track_live.scss | 42 ++++++ .../website_event_track_live_templates.xml | 33 +++++ .../website_event_track_live/views/assets.xml | 14 ++ .../views/event_track_templates_list.xml | 22 ++++ .../views/event_track_templates_page.xml | 53 ++++++++ .../views/event_track_views.xml | 16 +++ 16 files changed, 546 insertions(+) create mode 100644 addons/website_event_track_live/controllers/__init__.py create mode 100644 addons/website_event_track_live/controllers/session.py create mode 100644 addons/website_event_track_live/controllers/track_live.py create mode 100644 addons/website_event_track_live/data/event_track_demo.xml create mode 100644 addons/website_event_track_live/models/__init__.py create mode 100644 addons/website_event_track_live/models/event_track.py create mode 100644 addons/website_event_track_live/static/src/js/website_event_track_live.js create mode 100644 addons/website_event_track_live/static/src/js/website_event_track_suggestion.js create mode 100644 addons/website_event_track_live/static/src/scss/website_event_track_live.scss create mode 100644 addons/website_event_track_live/static/src/xml/website_event_track_live_templates.xml create mode 100644 addons/website_event_track_live/views/assets.xml create mode 100644 addons/website_event_track_live/views/event_track_templates_list.xml create mode 100644 addons/website_event_track_live/views/event_track_templates_page.xml create mode 100644 addons/website_event_track_live/views/event_track_views.xml 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 = $(' + + + + + 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 @@ + + + + + + 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 @@ + + + + + + + + + + +