Stream-Design (WIP)

Eine MEAN-Stack WebApp, welche dynamisch Livestream (Twitch) Inhalte darstellt und als Design dienen soll.

Diese WebApp ist an Nutzer gerichtet, die einen eigenen Livestream auf Twitch.tv unterhalten und nach einer simplen Möglichkeit suchen, ihre gespielten Spiele darzustellen und zu administrieren, als auch bspw. Pause-Screens anzuzeigen.

Der MEAN-Stack umfasst folgende Technologien:

  • MongoDB
  • ExpressJS
  • Angular
  • Node.JS

Dieses Projekt ist meine erste Fullstack WebApp.

Technologiewahl

Ich habe mich aufgrund meiner vorherigen Erfahrungen mit Angular und Node.JS für diesen Stack entschieden. Zuvor habe ich beruflich lediglich mit AngularJS (Version 1.x) arbeiten dürfen. Die neueren Versionen von Angular haben mich besonders durch die Umstellung von JavaScript auf TypeScript interessiert.

MongoDB hat mich durch den simplen Einstieg und die JavaScript-Nähe (JSON-Dokumente) gereizt.

Insgesamt ist der MEAN-Stack sehr populär, wodurch es mehr Tutorials und Beispiel-Apps gibt.

Zum Deployment habe ich mich für Docker-Container entschieden, welche auf einer beliebigen Umgebung bereitgestellt werden können. Aktuell sind es zwei Container (einer für das Frontend und einer für das Backend), welche auf meinem NAS aktiv sind. Das ermöglicht mir, meine App 24/7 zu benutzen und stellt, durch die Streamerunabhängige Instanz, die Aktualität der Daten sicher.

Ziel

Diese Anwendung soll es dem Nutzer ermöglichen, Spiele, welche im Livestream gespielt wurden, simpel und übersichtlich darzustellen. Das soll dem Zuschauer einen Überblick darüber geben, welche Spiele der jeweilige Livestreamer spielt.

Zudem soll der Nutzer die angezeigten Spiele, neben der automatisierten Variante, auch manuell administrieren können.

Um den einen Zuschauern zu visualisieren, dass es aktuell eine Pause gibt, wurde der Intermission/Pause-Screen entworfen.

Priorität hat die Nutzerfreundlichkeit und der geringe Pflege-Aufwand. Diese WebApp soll die Daten möglichst eigenständig aktualisieren und in der eigenen Datenbank hinterlegen, sodass keine Abhängigkeiten zur Twitch-API bestehen.

Diese WebApp soll selfhosted sein. Ich habe keinerlei Interesse an den Daten anderer und jeder sollte Kontrolle über seine eigenen Daten haben, daher die Docker-Container.

Umsetzung

  1. Damit der Nutzer Zugriff auf die Twitch API hat, werden im Docker-Container Umgebungsvariablen hinterlegt.
  2. Vom Backend wird eine “Subscription” bei einem Webhook-Dienst von Twitch registriert.
  3. Über den Webhook werden die aktuellen Daten vom Twitch-Server zum WebApp-Backend gesendet.
  4. Das Backend legt die aktuellen Daten in der MongoDB ab
  5. Per Websocket wird ein Event gesendet, auf das das Frontend “hört”.
  6. Das Frontend wird mit den aktualisierten Daten neu gerendert.
// Backend
webhookExpress.post('/', (req, res) => {
  if (req.twitch_hub && req.twitch_hex === req.twitch_signature) { // Step 3: Incoming Request
    console.log("[Webhook POST] Stream Change!"); // Webhook sends data for each stream change (eg. changed game, title, ...)

    twitchService.getGameById(req.body.data[0].game_id) // get game via Twitch API
    .then(game => {
      gameService.createGame({ // Step 4: Create new game in MongoDB
        id: game.id,
        title: game.name,
        image: game.box_art_url,
      })
      .then((game) => {
        console.log('[Twitch WebHook: Game]', game);
        io.emit('newGame', game.toJSON()); // Step 5: Event that the frontend listens to
      });
    });

    res.send('OK');
  } else {
    console.log("Wrong POST Request");
    res.send("Answer to wrong POST Request");
  }  
});
// Frontend
this.io.on('newGame', (game: Game) => { // Step 5: Frontend listening to 'newGame' event
    console.log(game);
    this.addNewGame(game);
});

Game-Seite

Die Nutzung von Angular bietet sich vor allem durch die Trennung von Darstellung und Logik an. Ein HTML-Template beinhaltet ausschließlich das, was dargestellt wird (ja, auch mit “Logik-Elementen” wie Schleifen, welche aber lediglich steuern, was dargestellt wird). Somit ist das HTML-Template für die Game-Seite sehr übersichtlich:

<div class="games">
  <div *ngFor="let game of gameList">
    <img [src]="game.image">
  </div>
</div>

Auch der strukturierte Aufbau in einzelnen Komponenten gefällt mir gut, weil beispielsweise auch die SCSS-Datei innerhalb der entsprechenden Komponente Anwendung findet, was wiederum die Übersichtlichkeit erhöht:

@import '../../theme/modals';
@import '../../theme/colors';

.games {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-gap: 1rem;

    div {
        padding: 1.5rem;

        img {
            width: 100%;
            height: auto;
            box-shadow: 0px 0px 10px 2px $shadow-color;
        }
    }
}

Selbst die Typescript-Datei ist überschaubar. Es wird eine Klasse definiert mit einigen wenigen Methoden und Attributen.

Ein Ausschnitt aus der Datei:

// Some Properties
public streamMode: string;
public newGame: Game = new Game();
gameList: Game[] = Array<Game>();

// Show/Hide Navigation based on streamMode
(this.streamMode === 'true') ?  this.navigationService.hide('true') : this.navigationService.hide('false');

// Get Games from Database
this.gameService.getGames('desc')
.subscribe((games: Array<Game>) => {
    games.forEach((game: Game) => {
    game.image = this.setBoxArtSize(game);
    });
    this.gameList = games;
});

Intermission-Seite

Diese Seite hat einen einfachen Hintergrund um diesen im Stream-Tool transparent zu machen, damit es einen “coolen Effekt” über einem Spiel ergibt.

Intermission-Screen im Spiel

Fairerweise muss ich für diese Seite die Lorbeeren an den CodePen Nutzer “julkuh“ geben.

Auf dieser Grundlage habe ich meine eigenen Farbcodes integriert und einen Farbverlauf implementiert und somit schlussendlich für meine Zwecke genutzt.

Administration-Seite

Die Tabelle ist das Äquivalent zu dem, was in der Datenbank gespeichert wird. Der Nutzer kann eigene Einträge anlegen und diese mit den entsprechenden Daten befüllen. Die Einträge werden aktuell nach dem Datum sortiert.

Auf dieser Seite ist der “Work in Progress”-Status besonders gut zu erkennen, da die Aktions-Buttons noch sehr rudimentär aussehen.

Der Details-Button ist für ein zukünftiges Feature angedacht und öffnet aktuell ein Modal in dem alle Clips zu dem jeweiligen Spiel aufgelistet werden.

Die “Edit” und “Delete” Buttons sind trivial.

Besonders hervorzuheben ist die Zeile zum hinzufügen eines neuen Eintrags, weil automatisch eine Bild-URL generiert wird, sobald der Titel eingegeben wird und es als Spiel von der Twitch API erkannt wird.

Auffällig sind die Platzhalter für die Größe der Bilder ({width} und {height}), diese Platzhalter werden auf der Game-Seite durch geeignete Pixel-Werte ersetzt (aktuell noch fest).

Ausblick

  • In Zukunft sollen auch Clips zu den Spielen administrierbar sein. Das heißt, dass wahlweise auch Clips zu bestimmten Spielen auf der Game-Seite abgespielt werden können. Die Clips werden von den Zuschauern erstellt und automatisch in der Datenbank indexiert.
  • Eine Kategorisierung der Spiele nach Status ist interessant, weil der Zuschauer dann weiß, ob Spiel X vielleicht öfter im Livestream zu sehen sein wird.
  • Mehr Konfiguration. Der Nutzer soll auch im Frontend IDs und Token hinterlegen können, damit keinerlei technisches Wissen zur Nutzung der App vorausgesetzt wird.
  • Tracken der Zeiten, wie lange ein Spiel im Livestream gespielt wurde um anhand dessen die Reihenfolge festzulegen (bspw. viel gespielte Spiele nach vorne).
  • Weiterentwicklung zur nutz- und skalierbaren WebApp um Nutzern einzelne Instanzen anzubieten, als Alternative zum selbst hosten.
  • Konfiguration der Darstellung
    • Kachelgröße selbst definieren
    • Intermission-Seite mit verschiedenen Darstellungsoptionen
  • Integration von “Widgets” um im Livestream anzuzeigen, wann jemand auf “Follow” geklickt hat o.ä.
  • Auf der Games-Seite weitere Informationen zum Spiel anzeigen

Vollumfängliches Stream-Design über eine WebApp. Das wäre wohl das “große Ziel”. Und dabei, als Abgrenzung von der Konkurrenz, die Interessen des Streamers und der Zuschauer berücksichtigen.