Closed Jean-Baptiste-Lasselle closed 9 months ago
j'ai documenté mon TwitchPlayer. je pense qu'il pourrait partir en Review
Twitch player isnt that bad, it's full reponsive when finishing the job as a frontend dev
we want our TwitchPlayer reponsive :
What we got from Twitch API Embedding Everything (https://dev.twitch.tv/docs/embed/everything/)
<!-- Add a placeholder for the Twitch embed -->
<div id="twitch-embed"></div>
This player is full responsive, depending its width, it will fold/unfold to keep at best in every screen resolution.
* With the layout option : `layout: 'video-and-chat'` the player will provide the chat, right or down the video depending the width, to fit into the best view.
But at the moment, the player wont be responsive with resize (web) or rotate (mobile), we need to attach listeners to events in order to detect resizing or rotation.
* Nothing really hard here
```js
const inject = document.createElement('script')
inject.type = "text/javascript"
inject.src = "https://embed.twitch.tv/embed/v1.js"
inject.onload = () => {
new Twitch.Embed("twitch-embed", {
width: 854,
height: 480,
channel: "monstercat",
layout: "video-and-chat",
// Only needed if this page is going to be embedded on other websites
parent: ["embed.example.com", "othersite.example.com"]
})
window.addEventListener('resize', () => {
...
})
}
document.getElementById("twitch-embed").append(inject)
<div id="twitch-embed"></div>
<div id="twitch-embed">
<script src="https://embed.twitch.tv/embed/v1.js"></script>
<iframe
src="https://embed.twitch.tv?autoplay=false&channel=radiojaune&height=400&layout=video-and-chat&parent=embed.example.com&parent=radiojaune.com&parent=localhost&referrer=http%3A%2F%2Flocalhost%3A3000%2F&width=800"
allowfullscreen=""
scrolling="no"
frameborder="0"
allow="autoplay; fullscreen"
title="Twitch"
sandbox="allow-modals allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
width="472.6666666666667"
height="485.5"></iframe>
</div>
<iframe>
const embedDiv = document.getElementById("twitch-embed")
for (var i=0; i < embedDiv.getElementsByTagName("iframe").length; i++) {
if (embedDiv.getElementsByTagName("iframe")[i].title == "Twitch") {
const frameId = i
window.addEventListener("resize", () => { ... })
}
}
embed.addEventListener(Twitch.Embed.VIDEO_READY, () => {
var player = embed.getPlayer()
player.play()
console.log("TwitchPlayer: VIDEO_READY")
})
embed.addEventListener(Twitch.Embed.VIDEO_PLAY, () => {
document.getElementById("twitch-embed").style.opacity = 1
console.log("TwitchPlayer: VIDEO_PLAY")
})
layout: 'video-and-chat'
So, we are able to have pretty responsive resized (web) or rotated (mobile) Player, but as we're providing layout: 'video-and-chat'
we can observe the video layer being cut under a 800px screen size ... (i wont comment)
Unfortunatly, layout
isnt an iframe property, but a script argument :
<iframe src="...tv?autoplay=false&channel=monstercat&height=400&layout=video-and-chat
&parent=..."
we need to update, the best way we can our iframe src property ( & make sure to update once, not zounds ), where layout
options stand.
we will keep the original script arguments & only switch layout=
value
const iframe = embedDiv.getElementsByTagName("iframe")[frameId]
if (
window.innerWidth < 800 &&
iframe.src.replace("layout=video-and-chat","layout=video") != iframe.src
) { // UPDATE ONCE (maybe 2)
iframe.src = iframe.src.replace("layout=video-and-chat","layout=video")
console.log("switching player src")
} else if (
window.innerWidth > 800 &&
iframe.src.replace("layout=video-and-chat","") == iframe.src
) { // UPDATE ONCE
iframe.src = iframe.src.replace("layout=video","layout=video-and-chat")
console.log("switching player src")
}
<div id="twitch-embed" class="absolute" style="opacity: 0.3; z-index:5;"></div>
<script type="text/javascript">
/**
* CONFIG YOUR FLAVOR
*/
const chatLayout = true // layout option [true: 'video-and-chat' | false: 'video']
const pixelRangeToSwitchSrcOptions = 800 // minimal pixel range to fire src option replacement when chatLayout=true (TwitchPlayer constraint (800 pixels), but may change), (window.innerWidth+1 to disable)
const widthRatio = 1.5 // TwitchPlayer width screen ratio
const heightRatio = 2 // TwitchPlayer height screen ratio
const channel = 'monstercat' // your channel
const webUrls = ["embed.example.com", "othersite.example.com"] // your network
const autoplay = true // Twitch.Embed.VIDEO_READY action
const verbose = true // console feedback on|off
// TWITCH API SCRIPT INJECTION
const inject = document.createElement("script")
inject.type = "text/javascript"
inject.src = "https://embed.twitch.tv/embed/v1.js"
inject.onload = () => { startTwitch() }
document.getElementById("twitch-embed").append(inject)
// TWITCHPLAYER INITIALISATION & DEDICATED LISTENERS
function startTwitch() {
embed = new Twitch.Embed("twitch-embed", {
width: (window.innerWidth/widthRatio),
height: (window.innerHeight/heightRatio),
channel: channel,
layout: "video"+((window.innerWidth > pixelRangeToSwitchSrcOptions && chatLayout)?"-and-chat":""),
parent: webUrls
})
// TWITCH API
embed.addEventListener(Twitch.Embed.VIDEO_READY, () => {
var player = embed.getPlayer()
if (autoplay) player.play()
if (verbose) console.log("TwitchPlayer: VIDEO_READY")
})
embed.addEventListener(Twitch.Embed.VIDEO_PLAY, () => {
// KEEP CREATIVE/INTERACTIVE HERE
document.getElementById("twitch-embed").style.opacity = 1
if (verbose) console.log("TwitchPlayer: VIDEO_PLAY")
})
// MORE RESPONSIVTY (WEB)
const embedDiv = document.getElementById("twitch-embed")
for (var i=0; i < embedDiv.getElementsByTagName("iframe").length; i++) {
// FIND THE RIGHT NODE
if (embedDiv.getElementsByTagName("iframe")[i].title == "Twitch") {
if (verbose) console.log("attaching resize Events on TwitchPlayer-frame["+i+"]")
const frameId = i
window.addEventListener("resize", () => {
// IFRAME SIZES
embedDiv.getElementsByTagName("iframe")[frameId].width = window.innerWidth/widthRatio
embedDiv.getElementsByTagName("iframe")[frameId].height = window.innerHeight/heightRatio
const iframe = embedDiv.getElementsByTagName("iframe")[frameId]
// IFRAME SRC OPTIONS SWITCH
if (
window.innerWidth < pixelRangeToSwitchSrcOptions &&
iframe.src.replace("layout=video-and-chat","") != iframe.src
) { // UPDATE ONCE
iframe.src = iframe.src.replace("layout=video-and-chat","layout=video")
if (verbose) console.log("switching player src")
} else if (
window.innerWidth >= pixelRangeToSwitchSrcOptions &&
iframe.src.replace("layout=video-and-chat","") == iframe.src
) { // UPDATE ONCE
iframe.src = iframe.src.replace("layout=video","layout=video-and-chat")
if (verbose) console.log("switching player src")
}
})
}
}
}
</script>
<TwitchPlayer client:only="preact" />
aussi, en changeant le <div id='twitch-embed'>
si l'on force la taille du container qui accueil le twitchplayer, & que l'on intervient sur le width/height de l'iframe injectée pour les passer a "100%" :
<div id="twitch-embed" class="absolute grid justify-items-center items-center min-w-[80%] min-h-[50%]" style="opacity: 0.3; z-index:5;"></div>
contient le player de facon responsive, alors je change mon script:
// FIND THE RIGHT NODE
if (embedDiv.getElementsByTagName("iframe")[i].title == "Twitch") {
if (verbose) console.log("attaching resize Events on TwitchPlayer-frame["+i+"]")
const frameId = i
window.addEventListener("resize", () => {
...
en
// FIND THE RIGHT NODE
if (embedDiv.getElementsByTagName("iframe")[i].title == "Twitch") {
if (verbose) console.log("attaching resize Events on TwitchPlayer-frame["+i+"]")
const frameId = i
embedDiv.getElementsByTagName("iframe")[frameId].width = "100%"
embedDiv.getElementsByTagName("iframe")[frameId].height = "100%"
window.addEventListener("resize", () => {
...
& on peux desormais se passer des eventsListeners 'resize' (concernant le resize, car il restera pertinent de changer le layout pour les petites resolutions, a moins de trouver une autre solution)
Le player Twitch reagit tout aussi bien & naturellement
fait interressant, dans cette configuration, le reponsive naturel du player masque le layer video en dessous de 670px de largeur, alors que precedement, c'etait en dessous de 800px
c'est bien mieux que ça, le chargement naturel pour les petites resolutions affiche le layer video plutot que le chat seulement, l'affichage de chaques mobile dans la console est pile poil
pushed sur features/calendar
pourtant , coté console :
& coté DOM:
---
---
<div
id="twitch-embed"
class="absolute grid justify-items-center items-center min-w-[80%] min-h-[50%] opacity-30 z-5"
></div>
<script type="text/javascript" src="https://embed.twitch.tv/embed/v1.js" ></script>
<script>
const chatLayout = true // layout option [video|video-and-chat]
const pixelRangeToSwitchSrcOptions = 799 // minimal pixel range to fire src option replacement when chatLayout=true (TwitchPlayer constraint (800), but may change), (windowDimensions.width+1 to disable)
const widthRatio = 1.5 // TwitchPlayer width screen ratio
const heightRatio = 2 // TwitchPlayer height screen ratio
const channel = 'radiojaune' // your channel
const webUrls = ["embed.example.com", "othersite.example.com"] // your network
const autoplay = true // Twitch.Embed.VIDEO_READY action
const verbose = true // console feedback on|off
embed = new Twitch.Embed("twitch-embed", {
width: "100%",
height: "100%",
channel: channel,
layout: "video"+((window.innerWidth>pixelRangeToSwitchSrcOptions && chatLayout)?"-and-chat":""),
parent: webUrls
});
embed.addEventListener(Twitch.Embed.VIDEO_READY, () => {
var player = embed.getPlayer()
if (autoplay) player.play()
if (verbose) console.log("TwitchPlayer: VIDEO_READY")
});
embed.addEventListener(Twitch.Embed.VIDEO_PLAY, () => {
document.getElementById("twitch-embed").style.opacity = 1;
document.getElementById("calendarContainer").style.display = "none";
if (verbose) console.log("TwitchPlayer: VIDEO_PLAY")
});
</script>
petit soucis avec le domaine ancestor depuis https://justincurieux_feature_boris_twitch.surge.sh/
note:
test de ce matin : j'ai push la meme page html basic twitchplayer, mais en prenant soin de raccourcir au mieux le cname.
ca marche, voir quel serait la limite de taille du cname/domain, & le score SEO qui prend encore
concernant l'opacité, nous n'utiliserons plus l'event TWITCH.PLAY, mais une transition de quelques secondes qui ramenera le player twitch opaque
Nous avons actuellement 4 composants TwitchPlayer:
import { h } from 'preact'
export function TwitchPlayer() {
/**
* CONFIG YOUR FLAVOR
*/
const chatLayout = true // layout option [video|video-and-chat]
const channel = 'monstercat' // your channel
//const webUrls = '"surge.sh"' // your network
const autoplay = true // Twitch.Embed.VIDEO_READY action
const verbose = true // console feedback on|off
const embedTwitchScript = '"use strict";'+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
'const inject = document.createElement("script");'+
'inject.type = "text/javascript";'+
'inject.src = "https://embed.twitch.tv/embed/v1.js";'+
'inject.onload = () => { startTwitch() };'+
'document.getElementById("twitch-embed").append(inject);'+
'function startTwitch() {' +
'let embed = new Twitch.Embed("twitch-embed", {'+
'width: "100%",'+
'height: "100%",'+
'theme: "light",'+
'autoplay: '+autoplay+','+
'channel: "'+channel+'",'+
'muted: true,'+
'layout: "video'+((chatLayout)?"-and-chat":"")+'",'+
'parent: []'+
'});'+
'};'
return (
<>
<div id="twitch-embed" class="absolute grid justify-items-center items-center min-w-[80%] min-h-[50%] z-2 transition-opacity ease-linear duration-1000 from-100 to-30 "></div>
{
h(
'script',
{ type: 'text/javascript'},
embedTwitchScript
)
}
</>
)
}
* un composant .tsx (preact#2) qui charge le script remote-twitch conventionnelement via une balise script & son src, puis compil une balise script contenant le code d'intanciation de la Class TWITCH
```js
import { h } from 'preact'
export function TwitchPlayer2() {
const embedTwitchScript = 'var embed = new Twitch.Embed(\'twitch-embed\', {'+
'width: \'100%\','+
'height: \'100%\','+
'allowfullscreen: true,'+
'autoplay: true,'+
'channel: \'radiojaune\','+
'layout: \'video-and-chat\','+
'parent: [\'radiojaune.com\']'+
'});'
return (
<>
<div
id="twitch-embed"
class="absolute grid justify-items-center items-center min-w-[80%] min-h-[50%] opacity-30 z-3"
></div>
<script type="text/javascript" src="https://embed.twitch.tv/embed/v1.js" ></script>
{
h(
'script',
{ type: 'text/javascript'},
embedTwitchScript
)
}
</>
)
}
un composant .tsx (preact#3) qui charge le script remote-twitch conventionnelement via une balise script & son src & appelle le code d'instanciation de la meme facon avec un .js local (/twitch.js)
export function TwitchPlayer3() {
return (
<>
<div
id="twitch-embed"
class="absolute grid justify-items-center items-center min-w-[80%] min-h-[50%] z-2"
></div>
<script
type="text/javascript"
src="https://embed.twitch.tv/embed/v1.js"
></script>
<script
type="text/javascript"
src="/twitch.js"
></script>
</>
)
}
Chacuns de ces composant a été nettoyer de ses EventListeners
nos composant preact #2 & #3 plantent des lors qu'ils recoivent la client directive only="preact"
Restent muets (pas de player, pas d'erreures) avec les autres directives
& enfin fonctionnent normalement des lors qu'on leur supprime leur client directive, et ne sont inclus dans une island astro
le seul composant hydraté fonctionnel pour l'instant est le composant #1 avec sa client directive only="preact"
Nous allons donc faire une novuelle passe de travail pour trouver d'autres mode de conception. À terme, mais pas pour la première livraison Justn Curieux,nous essaierons par exemple un build wasm, pour embarquer toutes les dépendances avec le composant lui-même
Une autre catégorie de pistes, consiste à regarder tous les composants react que l'on trouve pour faire un twitch player.
En voici un premier que j'ai testé dans un projet Astro preact , basé sur le template astro landing page que l'on connaît bien :
pnpm add -D react@npm:@preact/compat react-dom@npm:@preact/compat
# https://www.npmjs.com/package/react-twitch-embed
# https://github.com/moonstar-x/react-twitch-embed
pnpm add --save react-twitch-embed
XTwitchPlayer.tsx
avec le code suivant : import React from 'preact/compat';
import { TwitchClip } from 'react-twitch-embed'; // <TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted />
const XTwitchPlayer = () => {
return (
<TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted />
);
};
export default XTwitchPlayer;
*.astro
, je vais utiliser <TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted />
ce composant a déjà une qualité : il est vraiment responsive, j'ai testé. Sa performance, nous verrons.
DOMContentLoaded
"Le Twitch Player est un problème pour la performance et les métrique lighthouse
Nous allons donc faire une novuelle passe de travail pour trouver d'autres mode de conception. À terme, mais pas pour la première livraison Justn Curieux,nous essaierons par exemple un build wasm, pour embarquer toutes les dépendances avec le composant lui-même
Les composants React des autres
Une autre catégorie de pistes, consiste à regarder tous les composants react que l'on trouve pour faire un twitch player.
En voici un premier que j'ai testé dans un projet Astro preact , basé sur le template astro landing page que l'on connaît bien :
- step 1, il faut ajouter correctement les dépandances preact/compat :
pnpm add -D react@npm:@preact/compat react-dom@npm:@preact/compat
- step 2, ajouter la dépendance du composant react que l'on souhaite utiliser ds notre projet astro :
# https://www.npmjs.com/package/react-twitch-embed # https://github.com/moonstar-x/react-twitch-embed pnpm add --save react-twitch-embed
- Step 3, Ajouter un
XTwitchPlayer.tsx
avec le code suivant :import React from 'preact/compat'; import { TwitchClip } from 'react-twitch-embed'; // <TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted /> const XTwitchPlayer = () => { return ( <TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted /> ); }; export default XTwitchPlayer;
- step 4, dans un
*.astro
, je vais utiliser<TwitchClip clip="AdventurousBusyWormTwitchRaid-7vDEE8L5ur9j9dzi" autoplay muted />
ce composant a déjà une qualité : il est vraiment responsive, j'ai testé. Sa performance, nous verrons.
Quelques références de discussions sur le sujet "Le Twitch embed démoli la perf de mon site web"
- https://discuss.dev.twitch.tv/t/how-to-mitigate-huge-performance-impact-of-a-twitch-embed/28182/2 là il parle d'un évènement "
DOMContentLoaded
"
J'ai trouvé un pattern applicable à tout "embedded player" , qui permet de résoudre le problème de performances, cf. https://github.com/3forges/juste-un-curieux/pull/20#issuecomment-1703995194
Très intéressant, sur lighthouse :
tailwindcss-animated
(un plugin tailwindcss ) https://justincurieux-feature-crt-crk6iqwz9-jean-baptiste-lasselle.vercel.app/#_$ git rev-parse HEAD
e4fc42c3fb741c0593bac2eadd1ecbd535b9df7b
75%
au lieu de 97%
J'ai constaté que la performance était toujours exactement au même niveau (75%) .
Hormis les animations tailwind css, je n'vais apporté qu'un seul changement : j'avais utilisé une "cascade de composants preact", (un compoant , qui importe un composant , qui importe un composant - 3 niveaux), le tout hydraté avec client:only="preact"
.
J'ai retiré ces changements impliquant "un Tsx, qui importe un TSX, qi importe un TSX" : et là ça y est, j'ai récupéré toute ma performance.
Moralité: l'hydratation, oui, ça peut coûter cher.
https://justincurieux-feature-crt-6km3pfj4r-jean-baptiste-lasselle.vercel.app/
$ git rev-parse HEAD
f8243add2ac4f3188fa18089605796b64f1a2d57
tailwindcss-animated
et là j'ai à nouveau obtenu un super score :) :
Donc les animatiosn css avec tailwind ne tuent pas du tout mon score lighthouse, ou ma performance. cf. https://justincurieux-feature-crt-9lvgs33kh-jean-baptiste-lasselle.vercel.app/ et :
$ git rev-parse HEAD
e6afdba01c78a45fd687ab6f9ab5458833803c75
De la même manière que le hamburger menu dans ~/components/header.astro
Tiens pour le "On Air" :
Une fois cela terminé:
Les deux applis web, cela va faire juste, en terme de temps pour la prochaine livraison, donc on fera le scénario suivant :
pnpm run toggle:onair
, qui va modifier le code source (ou plutôt la configuration), pour que le ONAIR=true et donc changer le css appliqué au deux boutons, de ce côté c'est une configuration config.ts
qu'il nous faudrait typiquement, avec une valeur process.env.ONAIR
, dont la valeur par défaut est false
, export ONAIR=true && pnpm run build
serace qui sera executé par pnpm run toggle:onair
.Reste ensuite simplement à faire la git release et le déploiement s'opère automatiquement. C'est l'appli web contrôlant OBS, qui va exécuter automatiquement cela, et le live ne commence que lorsque le déploiement est terminé. Avant le déploiement il est obligatoire de choisir un overlay, ce qui sélectionne un thème Tailwind CSS, que j'appliquerai aussi au déploiement ? (thème par défaut idem proces.env.DEFAULT_TW_THEME
. J'ai ajouté une animation tailwindcss
de type animate-typing
, et impecc atoujors la perf au top:
$ git rev-parse HEAD
1f1e3077c7f1f7bedc15e6ef7d6ffb96ff1f45d1
https://flowbite.com/docs/components/indicators/#badge-indicator
L'indicateur pourra alors aussi servir de bouton pour le share ?
On en reste là où le composant en est, à savoir src/components/TwitchPlayerX.tsx
, le bouton play sera amélioré plus tard avec une nouvelle issue
On veut un Player Twitch sur le premier écran en haut de page, Il n'est pas évident de rendre complètement responsive le player,