Appearance
kapp.medias
Pour les boucles et animations synchronisées, utiliser playClip ou playComposition : contrat des compositions média. Ces méthodes s’ajoutent aux lecteurs existants sans changer leurs retours ni leurs règles de fin.
Placement horizontal et vertical
Un média de préférence garde position: {x,y,w,h,portrait?: {x,y,w,h}|null}. playMedia et KMedia résolvent ce placement et déplacent les images/vidéos en cours sans recommencer la lecture. Sans variante, le placement horizontal est réduit et centré. Une écriture omettant portrait conserve la variante ; portrait:null la supprime. Fichier, volume et timeline restent communs. Voir le guide vertical.
Les positions explicites des options de lecture, de playImage/playVideo ou de setPosition utilisent les pixels logiques du cadre courant, sans conversion automatique. Le parent applique déjà le zoom. Ne multipliez pas par kapp.overlay.layout.scale.
Jouer des sons, images et vidéos sur l'overlay. Pluriel : kapp.medias (pas kapp.media).
Service partagé appli + widget : API identique.
Quand l'utiliser (vs alternative)
- Préfère
kapp.medias.*à un<audio>/<video>brut en HTML : c'est routé vers le bon contexte overlay et géré par le moteur. - Pour jouer un média déjà uploadé sur le channel : récupère ses métadonnées avec
kapp.medias.get(name), puis passe le fichier àplayMediadans un objetMediaData. Cette voie impose Web Audio pour les vidéos de la plateforme.
Vidéos, Web Audio et CORS (V2)
KVideo conserve la vidéo HTML en streaming et envoie son audio dans l'AudioContext de l'instance appli/widget. La fin ou l'arrêt d'un lecteur déconnecte uniquement sa source. Le contexte est fermé par le SDK après les hooks de sortie de l'instance, jamais à la fin d'un média. Ne fermez pas vous-même kapp.medias.audioContext.
- Vidéos Channel, bibliothèque publique et préférences lues via
KMedia/playMedia/playLibrary: Web Audio obligatoire. Une erreur de chargement rejette la promesse ; aucun fallback natif ne masque un défaut CORS de la plateforme. - URL libres (
playVideo/KVideo) : chargement aveccrossOrigin="anonymous", puis une seule tentative native sur un nouvel élément si le chargement initial émet une erreur média. Le navigateur ne permet pas de distinguer sûrement CORS, réseau et codec. Le logKVideo.native-fallbackindique ce mode dégradé, qui reste exposé au bug du mélangeur audio natif de Chromium. - Aucun fallback après le démarrage, sur un refus d'autoplay, ou si l'
AudioContextne peut pas reprendre.playSync()rejette ces erreurs. - Les médias propres à l'appli peuvent garder des URL relatives (
assets/intro.webm). Leur serveur doit autoriser CORS, y compris pour une iframe sandboxée dont l'origine vautnull. Les serveurs locaux Kappapps et DEV HUB le font ; un serveur tiers doit envoyerAccess-Control-Allow-Origin: *sans cookies requis, sur la réponse finale et les redirections. - Les éléments HTML créés directement, Phaser et les lecteurs externes ne sont pas interceptés. Ce correctif concerne les lecteurs du SDK V2 ; V1 reste inchangé.
Fichiers locaux du HUB streamer
Le HUB streamer peut partager un dossier depuis Outils → Avancé → Dossier local…. Le partage est désactivé initialement ; son activation, son dossier et son port sont mémorisés et repris au démarrage du HUB, même sans connexion au compte Kappapps. Le port par défaut est 7582, indépendant du serveur de commandes du HUB.
À l’activation ou au changement de port, le HUB connecté enregistre son adresse dans la préférence globale local_files_url du Channel. Sans connexion, le partage reste localement disponible et l’envoi attend la connexion ; un échec réseau est retenté. Seule l’adresse est envoyée, jamais le chemin Windows ni les fichiers. Elle est aussi modifiable ou effaçable dans Préférences → Général → Adresse du dossier local (HUB). Le redémarrage du HUB conserve une correction manuelle déjà faite ; une nouvelle activation ou un changement de port republie l’adresse du HUB. La désactivation retire l’adresse qu’il avait publiée si elle n’a pas été remplacée. Quitter le HUB conserve la préférence pour le prochain lancement.
Service kapp.localFiles
Service partagé appli + widget, sans scope applicatif supplémentaire. Il utilise la préférence courante du Channel, y compris après sa mise à jour pendant une exécution. Il ne sonde pas le PC au démarrage et n’utilise aucune adresse par défaut si le Channel n’en a pas configuré.
configured: boolean,baseUrl: string | null: configuration connue, sans test réseau.configuredne garantit pas que le HUB est ouvert ou accessible.url(path = ''): string | null: URL correctement encodée, sans requête.list(path = ''): Promise<LocalFileEntry[]>: enfants directs du dossier courant, racine par défaut ; chaque entrée aname,type,urletpathrelatif décodé.text(path): Promise<string | null>,json<T = unknown>(path): Promise<T | null>,blob(path): Promise<Blob | null>: lecture de texte, JSON ou binaire. Le typeTne valide pas le contenu JSON. Pour un média volumineux, utiliser son URL afin de conserver le streaming et les lectures partielles.
Les chemins sont relatifs et non pré-encodés, par exemple images/été #1.png. entry.path peut être repassé à list, text, json ou blob. entry.url est directement utilisable dans src ou kapp.medias.play*. Les traversées, fichiers cachés, chemins Windows et URL externes sont refusés.
Sans adresse configurée, list() renvoie [] et les autres méthodes renvoient null, sans requête. Avec une adresse configurée, une erreur HTTP (dont 404), réseau, permission navigateur, index invalide ou JSON illisible rejette la promesse : prévoir un try/catch selon le comportement souhaité. Les requêtes expirent après 15 secondes et sont annulées à la sortie de l’appli/widget. Les lectures ne passent pas par le backend Kappapps et n’envoient aucun cookie ni token Kappapps au HUB.
js
const files = await kapp.localFiles.list('videos/');
const video = files.find(file => file.type === 'file' && file.name.endsWith('.mp4'));
if (video) await kapp.medias.playVideo(video.url);Accès HTTP direct
Une URL telle que http://localhost:7582/images/logo.png s’utilise directement dans src ou comme source libre de kapp.medias.playImage / playAudio / playVideo. Ces fichiers ne sont pas des médias uploadés du channel : ne pas les chercher avec kapp.medias.get ou les passer comme référence à playMedia.
Un fetch('http://localhost:7582/') suivi de response.json() renvoie un index généré à chaque requête : {entries: [{name: 'images', type: 'directory', url: 'http://localhost:7582/images/'}, {name: 'config.json', type: 'file', url: 'http://localhost:7582/config.json'}]}. Chaque index contient uniquement les enfants directs, triés par nom avec les dossiers en premier. Les URL sont absolues et déjà encodées ; utiliser entry.url sans reconstruire le chemin. Appeler l’URL d’un dossier renvoie son index ; celle d’un fichier renvoie son contenu. Aucun fichier d’index n’est nécessaire ; un index.html reste accessible uniquement par son nom.
Le partage sert tous les types de fichiers statiques en lecture seule, sans exécution côté serveur, sans clé et avec CORS pour les iframes d’origine null. Le dossier et son index sont volontairement accessibles aux clients autorisés à joindre le service local ; il faut choisir un dossier destiné au partage. Les entrées commençant par un point et les liens/jonctions sortants sont exclus. Les index ne sont pas mis en cache ; les fichiers sont revalidés à chaque requête. Une modification du disque est disponible au prochain chargement, sans rechargement automatique du widget.
Limite connue : le HUB doit tourner sur le même PC que le widget. Chez un viewer ou sur un autre ordinateur,
localhostdésigne cet autre ordinateur. Un navigateur récent peut demander l’autorisation d’accès au réseau local, y compris pour un simplesrc. Un refus bloque égalementfetch, même avec CORS. Conserver le sandbox.
Méthodes
playAudio(source: string | GeneratedAudio, volume = 50, effects: KAudioEffect[] = []): Promise<KAudio>— joue un son.volume0-100. async. UnGeneratedAudioretourné partextToSpeech.generate()désactive la normalisation et les effets supplémentaires, car son fichier est déjà préparé.playImage(source: string, position = {}): Promise<KImage>— affiche une image.position:{x?, y?, w?, h?}(px). async.playVideo(source: string, volume = 50, position = {}): Promise<KVideo>— joue une vidéo. async.playMedia(media: MediaData, options = {}): Promise<KMedia>— avancé :media= objetMediaDatacomplet ({file, volume, position, timeline}), pas un simple chemin.get(name: string): Promise<ChannelMediaFileData | null>— métadonnées d'un média du channel par nom ({path, name, type, extension, …}). async.all(): Promise<ChannelMediaFileData[]>— tous les médias du channel. async.library(): Promise<LibraryMediaFileData[]>— tous les assets publiés de la bibliothèque publique Kappapps. async.getLibrary(slug: string): Promise<LibraryMediaFileData | null>— un asset public par son slug (identifiant stable, ex.kapp-ding).nullsi inconnu ou dépublié. async.playLibrary(slug: string, options = {}): Promise<KMedia | null>— joue directement un asset public par slug (son/vidéo/image selon son type).nullsi le slug ne résout pas. async.upload(blob: Blob, name = null): Promise<ChannelMediaFileData>— ajoute un fichier aux médias du channel. Le nom fourni est conservé après suppression des espaces aux extrémités ; sans nom (nullou argument omis), le serveur génère un nom unique. Appli : scopefile:write(file:readpourget/all). async.
Pour une image ou un son généré, les méthodes IA/TTS proposent une sauvegarde backend optionnelle : ne pas ajouter un upload() si save: true a déjà été demandé. L'upload de fichiers conserve son contrat.
Nommer un fichier envoyé
upload(blob, 'victoire.png') enregistre le nom victoire.png, dans une appli comme dans un widget. Le nom est un libellé : une extension n'est pas obligatoire et le type réel reste déterminé par le contenu. Il doit être non vide après trim, faire au plus 255 caractères et ne contenir ni séparateur de chemin, ni caractère de contrôle ; . et .. sont refusés. Un nom invalide rejette l'appel en 422.
Les noms explicites ne sont pas uniques dans le gestionnaire de fichiers. Deux envois portant le même nom créent deux fichiers distincts et ne remplacent pas l'existant. Choisir des noms distincts pour utiliser get(name) sans ambiguïté. Sans nom explicite, chaque envoi reçoit un nom différent, même lorsque les appels se suivent immédiatement.
L'envoi accepte au plus 5 Mo et les extensions détectées mp3, wav, mp4, mov, webm, webp, png, jpeg, jpg, gif ; un dépassement ou un type refusé provoque un rejet en 400. Les quotas du channel restent applicables. Les anciens SDK qui ne transmettent que le nom du fichier multipart le voient traité comme un nom fourni ; charger le SDK V2 à jour pour distinguer l'absence de nom et demander la génération automatique.
Timeline
timeline.startAtettimeline.endAtsont exprimés en millisecondes.- Sans
endAt, un son ou une vidéo va jusqu'à sa fin naturelle ; une image reste affichée jusqu'àstop()ou jusqu'à la fermeture du widget. - Une fin nulle, négative ou antérieure au début est ignorée afin de ne jamais créer un média de durée nulle.
Bibliothèque publique Kappapps
Des assets prêts à l'emploi (soundboards, images, vidéos) fournis par Kappapps, jouables sur n'importe quel channel sans aucun upload — l'asset est servi directement depuis le CDN, il n'est pas copié chez le streamer.
LibraryMediaFileData = ChannelMediaFileData + {slug, category, tags}. Le slug est l'identifiant stable à utiliser dans le code et dans les prefs.
js
// Jouer un son de la bibliothèque publique
await kapp.medias.playLibrary('kapp-ding', { volume: 70 });
// Parcourir le catalogue (assets publiés uniquement)
const sounds = (await kapp.medias.library()).filter(a => a.type === 'sounds');Dans une appli, un item de préférence mediaFile peut aussi avoir un asset public comme valeur par défaut (default: { library: 'kapp-ding' }) — voir médias des préférences. Les widgets n'ont pas ces préférences d'appli.
KAudioEffect (tableau d'effets pour playAudio) : radio, echo, slow, fast, left, right, half_speed, double_speed, double_pitch, half_pitch, neighbor, reverb, robot, disto, high, low.
Contrôler un média joué
Les méthodes play* renvoient un KMedia (KAudio, KVideo ou KImage) déjà lancé. Ses contrôles :
stop()arrête la lecture sans retirer l'élément ;remove()arrête puis retire définitivement le média. Synchrones, chaînables.setVolume(0..100)(sans effet sur une image) etsetPosition({x?, y?, w?, h?})(sans effet sur un son ; les champs omis sont conservés). Synchrones, chaînables.- Pour un
KMediaobtenu sans lecture (préchargé ou construit),await media.preload()charge sans jouer,await media.play()lance sans attendre la fin etawait media.playSync()lance puis attend la fin.
Exemple (V2, exécutable)
js
// Joue un son existant du channel à 70% + un effet radio
const media = await kapp.medias.get('coin');
if (media) {
await kapp.medias.playAudio(media.path, 70, ['radio']);
}Pièges
playAudio/playImage/playVideoacceptent un chemin (string).playAudioaccepte aussi le résultat complet detextToSpeech.generate().playMediaprend un objetMediaDatacomplet — ne lui passe pas le résultat brut deget()sans le wrapper.- Pour les fichiers Channel, privilégie
playMediaavec le fichier obtenu parget(). Pour un fichier embarqué dans l'appli, une URL relative réelle convient aux méthodesplay*par URL. N'invente pas de chemin. volumeest sur 0-100.- Slug ≠ name :
get(name)cherche dans les médias du channel par nom de fichier ;getLibrary(slug)/playLibrary(slug)cherchent dans la bibliothèque publique par slug. Ne les mélange pas. playLibraryrenvoienull(pas d'exception) si le slug est inconnu ou dépublié — gère ce cas.- Un asset dépublié disparaît de
library()/getLibrary()mais reste jouable via une pref qui le référençait déjà. - V1 mort :
kapp.playMedia(file)/kapp.media.play({url, volume})n'existent plus.