3forges / juste-un-curieux

Un site internet pour les curieux...
MIT License
0 stars 1 forks source link

Twitch player #3

Closed Jean-Baptiste-Lasselle closed 9 months ago

Jean-Baptiste-Lasselle commented 1 year ago

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,

BorisTherin commented 1 year ago

j'ai documenté mon TwitchPlayer. je pense qu'il pourrait partir en Review

Make your Embeded Twitch Player pretty responsive 🔧

Twitch player isnt that bad, it's full reponsive when finishing the job as a frontend dev

Goals


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">
  <script src="https://embed.twitch.tv/embed/v1.js"></script>
  <iframe 
    src="https://embed.twitch.tv?autoplay=false&amp;channel=radiojaune&amp;height=400&amp;layout=video-and-chat&amp;parent=embed.example.com&amp;parent=radiojaune.com&amp;parent=localhost&amp;referrer=http%3A%2F%2Flocalhost%3A3000%2F&amp;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>
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", () => { ... })
    }
}

Benefits: keep connected to Twitch-Api


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")
    })

The bad thing about TwitchPlayer & layout: 'video-and-chat'


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")
}

Final script ✅


<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>

ResponsiveTwitchPlayer.md

🎁 Feel free to git my preact component for 🚀Astro Island


BorisTherin commented 1 year ago

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

BorisTherin commented 1 year ago

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

BorisTherin commented 1 year ago

pourtant , coté console : image image

& coté DOM: image

BorisTherin commented 1 year ago

script final TwitchPlayer.astro

---
---
<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>
BorisTherin commented 1 year ago

petit soucis avec le domaine ancestor depuis https://justincurieux_feature_boris_twitch.surge.sh/

image

Jean-Baptiste-Lasselle commented 1 year ago

note:

BorisTherin commented 1 year ago

test de ce matin : j'ai push la meme page html basic twitchplayer, mais en prenant soin de raccourcir au mieux le cname.

https://spik-.surge.sh/

image

ca marche, voir quel serait la limite de taille du cname/domain, & le score SEO qui prend encore

BorisTherin commented 1 year ago

concernant l'opacité, nous n'utiliserons plus l'event TWITCH.PLAY, mais une transition de quelques secondes qui ramenera le player twitch opaque

BorisTherin commented 1 year ago

Nous avons actuellement 4 composants TwitchPlayer:

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
                )
            }
        </>
    )
}

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" image

Restent muets (pas de player, pas d'erreures) avec les autres directives image

& enfin fonctionnent normalement des lors qu'on leur supprime leur client directive, et ne sont inclus dans une island astro image

le seul composant hydraté fonctionnel pour l'instant est le composant #1 avec sa client directive only="preact"

Jean-Baptiste-Lasselle commented 1 year ago

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 :

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
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;

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"

Jean-Baptiste-Lasselle commented 1 year ago

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"

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

Jean-Baptiste-Lasselle commented 1 year ago
Jean-Baptiste-Lasselle commented 1 year ago

Très intéressant, sur lighthouse :

$ git rev-parse HEAD
e4fc42c3fb741c0593bac2eadd1ecbd535b9df7b

image

Après avoir supprimé toutes les animations que j'avais ajoutées

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

Puis j'ai rajouté un peu d'animation et le package tailwindcss-animated

et là j'ai à nouveau obtenu un super score :) :

image

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

Et comment vais-je implémenter le "drawer" ?

De la même manière que le hamburger menu dans ~/components/header.astro

Jean-Baptiste-Lasselle commented 1 year ago

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 :

Petit ajout design "ON AIR"

J'ai ajouté une animation tailwindcss de type animate-typing, et impecc atoujors la perf au top:

$ git rev-parse HEAD
1f1e3077c7f1f7bedc15e6ef7d6ffb96ff1f45d1

image

Jean-Baptiste-Lasselle commented 1 year ago

https://flowbite.com/docs/components/indicators/#badge-indicator

L'indicateur pourra alors aussi servir de bouton pour le share ?

Jean-Baptiste-Lasselle commented 9 months ago

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