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:
Dieses Projekt ist meine erste Fullstack WebApp.
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.
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 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.
// 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);
});
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;
});
Diese Seite hat einen einfachen Hintergrund um diesen im Stream-Tool transparent zu machen, damit es einen “coolen Effekt” über einem Spiel ergibt.
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.
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).
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.