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

✨(video_player) lazy load embed video player #2171

Open
wants to merge 1 commit 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
1 change: 1 addition & 0 deletions src/frontend/js/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export enum IconTypeEnum {
TWITTER = 'icon-twitter',
UNIVERSITY = 'icon-univerity',
WARNING = 'icon-warning',
VIDEO_PLAY = 'icon-video-play',
}

export const Icon = ({ name, title, className = '', size = 'medium', ...props }: Props) => {
Expand Down
35 changes: 35 additions & 0 deletions src/frontend/scss/components/_subheader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,41 @@ $r-subheader-search-title-width: 19rem !default; // aligned on computed search r
position: relative;
padding-bottom: 56.25%; // Aspect ratio 16/9

.video-player-image {
img {
filter: brightness(0.85);
object-fit: cover;
}
img,
span {
position: absolute;
width: 100%;
top: 0;
bottom: 0;
margin: auto;
}
span {
text-align: center;
font: 48px/1.5 sans-serif;
fill: white;
display: flex;
justify-content: center;
align-items: center;
}
span svg {
transition: 0.5s;
width: 85px;
height: 85px;
}
img:hover,
span:hover svg {
fill-opacity: 1;
filter: drop-shadow(3px 3px 30px rgb(0 0 0 / 0.65));
}
span svg {
filter: drop-shadow(3px 3px 12px rgb(0 0 0 / 0.25));
}
}
iframe {
height: 100%;
position: absolute;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,60 @@
{% load i18n cms_tags %}
{% load i18n cms_tags extra_tags thumbnail static %}
{% comment %}
This is a copy of original template from plugin just to clean <iframe> from
obsolete attribute "frameborder" and invalid "allowfullscreen" attribute value.

For performance reasons instead of loading the video iframe directly,
it changes the default template with an hidden iframe that is only visible
when the user clicks on the image with a big play icon '▶', the image comes
from the video poster or the course cover.
Only after the user clicks on the play icon '▶', the browser loads the external
video player iframe.
Additionaly, it tries to autoplay the external video player, this feature depends
on the browser and external video platform implementations.
{% endcomment %}

{% if instance.embed_link %}
{# show iframe if embed_link is provided #}
<div class="aspect-ratio">
<a class="video-player-image" onclick="this.style.display='none'; this.nextSibling.style.display='block'; this.nextSibling.src=this.nextSibling.getAttribute('data-src');" href="javascript:void(0)" title='{% trans "Click to play video" %}'>
{% if instance.poster %}
<img
src='{% thumbnail instance.poster.url 300x170 replace_alpha='#FFFFFF' crop upscale subject_location=instance.poster.subject_location %}'
srcset='
{% thumbnail instance.poster 300x170 replace_alpha='#FFFFFF' crop upscale subject_location=instance.poster.subject_location %} 300w
{% if instance.poster.width >= 600 %},{% thumbnail instance.poster 600x340 replace_alpha='#FFFFFF' crop upscale subject_location=instance.poster.subject_location %} 600w{% endif %}
{% if instance.poster.width >= 900 %},{% thumbnail instance.poster 900x510 replace_alpha='#FFFFFF' crop upscale subject_location=instance.poster.subject_location %} 900w{% endif %}
'
sizes='(max-width:62em) 100vw, 660px'
alt='{% if instance.poster.default_alt_text %}{{ instance.poster.default_alt_text }}{% else %}{% trans 'course cover image' %}{% endif %}'
/>
{% else %}
{% placeholder_as_plugins "course_cover" as cover_plugins %}
{% blockplugin cover_plugins.0 %}
<img
src='{% thumbnail instance.picture 300x170 replace_alpha='#FFFFFF' crop upscale subject_location=instance.picture.subject_location %}'
srcset='
{% thumbnail instance.picture 300x170 replace_alpha='#FFFFFF' crop upscale subject_location=instance.picture.subject_location %} 300w
{% if instance.picture.width >= 600 %},{% thumbnail instance.picture 600x340 replace_alpha='#FFFFFF' crop upscale subject_location=instance.picture.subject_location %} 600w{% endif %}
{% if instance.picture.width >= 900 %},{% thumbnail instance.picture 900x510 replace_alpha='#FFFFFF' crop upscale subject_location=instance.picture.subject_location %} 900w{% endif %}
'
sizes='(max-width:62em) 100vw, 660px'
alt='{% if instance.picture.default_alt_text %}{{ instance.picture.default_alt_text }}{% else %}{% trans 'course cover image' %}{% endif %}'
/>
{% endblockplugin %}
{% endif %}
<span>
<svg aria-hidden="true">
<use href="#icon-video-play" />
</svg>
</span>
</a>
<iframe
title="{% if instance.label %}{{ instance.label }}{% else %}{% trans "Video" %}{% endif %}"
src="{{ instance.embed_link_with_parameters }}"
data-src="{{ instance.embed_link_with_parameters}}{% if '?' not in instance.embed_link_with_parameters %}?{% endif %}&autoplay=1"
{{ instance.attributes_str }}
allowfullscreen
style="display: none;"
></iframe>
</div>
{% with disabled=instance.embed_link %}
Expand All @@ -32,9 +75,9 @@

{% comment %}
# Available variables:
{{ instance.template }}
{{ instance.template }}
{{ instance.label }}
{{ instance.embed_link }}
{{ instance.poster }}
{{ instance.attributes_str }}
{% endcomment %}
{{ instance.attributes_str }}
{% endcomment %}
7 changes: 6 additions & 1 deletion src/richie/apps/core/templates/richie/icons.html
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<symbol id="icon-archive" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2 3H4.8C3.9 3 3 3.81 3 4.8v2.709c0 .648.387 1.206.9 1.521V19.2c0 .99.99 1.8 1.8 1.8h12.6c.81 0 1.8-.81 1.8-1.8V9.03c.513-.315.9-.873.9-1.521V4.8c0-.99-.9-1.8-1.8-1.8Zm-5.4 10.8h-3.6a.903.903 0 0 1-.9-.9c0-.495.405-.9.9-.9h3.6c.495 0 .9.405.9.9s-.405.9-.9.9Zm5.4-6.3H4.8V4.8h14.4v2.7Z" fill="currentColor" fill-rule="nonzero"/>
</symbol>


<symbol id="icon-video-play" viewBox="0 0 85 85" xmlns="http://www.w3.org/2000/svg">
<path style="fill:currentColor;stroke-width:.135807" d="M83.362 117.436a9.427 9.427 0 0 1-9.426 9.426 9.427 9.427 0 0 1-9.427-9.426 9.427 9.427 0 0 1 9.427-9.427 9.427 9.427 0 0 1 9.426 9.427z" transform="matrix(4.17778 0 0 4.17778 -269.089 -450.822)"/>
<path d="M204.11 0C91.388 0 0 91.388 0 204.111c0 112.725 91.388 204.11 204.11 204.11 112.729 0 204.11-91.385 204.11-204.11C408.221 91.388 316.839 0 204.11 0Zm82.437 229.971-126.368 72.471c-17.003 9.75-30.781 1.763-30.781-17.834V140.012c0-19.602 13.777-27.575 30.781-17.827l126.368 72.466c17.004 9.752 17.004 25.566 0 35.32z" style="fill:#fff" transform="scale(.19444)"/>
</symbol>

</defs>
</svg>
69 changes: 69 additions & 0 deletions tests/apps/core/test_videoplayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Test the custom video player with a performance improvement.
"""

import lxml.html # nosec
from cms.test_utils.testcases import CMSTestCase

from richie.apps.courses.factories import CourseFactory, VideoSample


class CoursesTemplatesCourseDetailRenderingCMSTestCase(CMSTestCase):
"""
Test the custom video player with a performance improvement.
"""

video_sample_without_image = VideoSample(
"Anant Agarwal: Why massively open online courses (still) matter",
None,
"//www.youtube.com/embed/rYwTA5RA9eU",
)

def test_templates_course_detail_teaser_video_cover_empty(self):
"""
When the `course_teaser` placeholder is filled with a VideoPlayerPlugin.
The course page should return an empty video cover image if:
- the video poster image is empty;
- the course page hasn't any `course_cover` placeholder.
"""
video_sample = self.video_sample_without_image
course = CourseFactory(fill_teaser=video_sample, should_publish=True)

response = self.client.get(course.extended_object.get_absolute_url())
self.assertEqual(response.status_code, 200)
html = lxml.html.fromstring(response.content)
iframe = html.cssselect(".subheader__teaser .aspect-ratio iframe")[0]
self.assertEqual(iframe.get("data-src"), video_sample.url + "?&autoplay=1")
self.assertEqual(iframe.get("title"), video_sample.label)
self.assertEqual(iframe.get("style"), "display: none;")
self.assertIn("allowfullscreen", iframe.keys())
# no video cover image
self.assertEqual(
len(html.cssselect(".subheader__teaser .aspect-ratio a img")), 0
)

def test_templates_course_detail_teaser_video_cover_from_course_cover(self):
"""
When the `course_teaser` placeholder is filled with a VideoPlayerPlugin.
The course page show the course cover image if:
- the video poster image is empty;
- the course page has a `course_cover` placeholder.
"""
cover_file_name = cover_file_name = "cover.jpg"
video_sample = self.video_sample_without_image
course = CourseFactory(
fill_teaser=video_sample,
fill_cover={"original_filename": cover_file_name},
should_publish=True,
)

response = self.client.get(course.extended_object.get_absolute_url())
self.assertEqual(response.status_code, 200)
html = lxml.html.fromstring(response.content)
iframe = html.cssselect(".subheader__teaser .aspect-ratio iframe")[0]
self.assertEqual(iframe.get("data-src"), video_sample.url + "?&autoplay=1")
self.assertEqual(iframe.get("title"), video_sample.label)
self.assertEqual(iframe.get("style"), "display: none;")
self.assertIn("allowfullscreen", iframe.keys())
img = html.cssselect(".subheader__teaser .aspect-ratio a img")[0]
self.assertIn(cover_file_name, img.get("src"))
4 changes: 2 additions & 2 deletions tests/apps/courses/test_templates_course_detail_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def test_templates_course_detail_teaser_video_cover_empty(self):
iframe = html.cssselect(".subheader__teaser .aspect-ratio iframe")[0]
self.assertIn("allowfullscreen", iframe.keys())
self.assertEqual(iframe.get("title"), video_sample.label)
self.assertEqual(iframe.get("src"), video_sample.url)
self.assertEqual(iframe.get("data-src"), video_sample.url + "?&autoplay=1")

def test_templates_course_detail_teaser_empty_cover_image(self):
"""
Expand Down Expand Up @@ -634,7 +634,7 @@ def test_templates_course_detail_teaser_video_cover_image(self):
iframe = html.cssselect(".subheader__teaser .aspect-ratio iframe")[0]
self.assertIn("allowfullscreen", iframe.keys())
self.assertEqual(iframe.get("title"), video_sample.label)
self.assertEqual(iframe.get("src"), video_sample.url)
self.assertEqual(iframe.get("data-src"), video_sample.url + "?&autoplay=1")


# pylint: disable=too-many-public-methods
Expand Down