Skip to content

Commit

Permalink
✨(video_player) lazy load embed video player
Browse files Browse the repository at this point in the history
Instead of loading the external video player,
it only loads the video player if the user clicks on the
big ▶ icon.
  • Loading branch information
igobranco committed Dec 2, 2024
1 parent eae94fe commit d1f0949
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 8 deletions.
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

0 comments on commit d1f0949

Please sign in to comment.