Parcourir la source

- Added http server

Parad0x il y a 5 ans
Parent
commit
0a358bfe6d

+ 1 - 1
app/obs/index.html

@@ -85,7 +85,7 @@
             }
         }
 
-        let ws = new WebSocket("ws://localhost:8045");
+        let ws = new WebSocket("ws://localhost:<$wsport$>");
         ws.onmessage = (msg)=>{
             let data = msg.data;
             let commandArray = JSON.parse(msg.data)

+ 1 - 1
app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "twich-points-alerts",
-  "version": "0.3.0",
+  "version": "0.4.0",
   "description": "Simple solution to play mp4 on stream triggered by twitch points",
   "main": "dst/main.js",
   "build": {

+ 1 - 0
app/src/web/AlertQueue.ts

@@ -4,6 +4,7 @@ import { EventEmitter } from "events";
 export type AlertElement = {
     filepath: string,
     id: string,
+    alertid: string,
     rewardEvent: PointsRedeemed,
 }
 

+ 180 - 0
app/src/web/HTTPServer.ts

@@ -0,0 +1,180 @@
+import http from "http"
+import {URL} from "url"
+import fs from "fs"
+
+function parseAccept(head: string): string[]{
+    let clientAccept = head.split(";");
+    if(!(0 in clientAccept)){
+        this.res.writeHead(400);
+        this.res.end("Problem with accept header");
+        return;
+    }
+    let acc = []
+    for(let it of clientAccept){
+        acc = acc.concat(acc, it.split(","))
+    }
+
+    return acc;
+}
+
+const playerpage: string = fs.readFileSync("./obs/index.html", "utf8");
+
+export class HTTPRequest{
+    private req: http.IncomingMessage;
+    private res: http.ServerResponse;
+
+    private url: URL;
+
+    constructor(req: http.IncomingMessage,res: http.ServerResponse){
+        this.req = req;
+        this.res = res;
+
+        this.url = new URL(req.url, `http://${req.headers.host}`);
+    }
+
+    notFound(){
+        this.res.writeHead(404);
+        this.res.end("Not Found");
+    }
+
+    internalError(){
+        this.res.writeHead(500);
+        this.res.end("Internal Server Error");
+    }
+
+    getPlayerIndex(){
+        this.res.writeHead(200);
+        this.res.end(playerpage.replace("<$wsport$>", 
+          window.settings.options.wsport.toString()));
+    }
+
+    async getMP4(){
+        let id = this.url.pathname.slice(5);
+
+        console.log(this.req.headers);
+
+        if(this.req.headers.accept){
+            let formats = parseAccept(this.req.headers.accept);
+            console.log(formats);
+            if(!(formats.includes("video/*") 
+                || formats.includes("video/mp4")
+                || formats.includes("*/*"))){
+                this.res.writeHead(400);
+                this.res.end("mp4 not supported");
+                return;
+            }
+        }
+
+        if(id in window.settings.options.alerts){
+            console.log(id);
+            let path = window.settings.options.alerts[id].filepath;
+            try {
+                let stat = await fs.promises.stat(path);
+                if(!stat.isFile()){
+                    throw new Error("This is not a file");
+                }
+
+                if(this.req.headers.range &&
+                     this.req.headers.range.startsWith("bytes=") ){
+                    let pos = 
+                      this.req.headers.range.slice(6).split("-");
+                    let start = parseInt(pos[0], 10);
+                    let total = stat.size;
+                    let end = pos[1] ? parseInt(pos[1], 10) : total - 1;
+
+                    this.res.setHeader("Accept-Ranges", "bytes");
+                    this.res.setHeader("Content-Type", "video/mp4");
+                    this.res.setHeader("Content-Range",
+                      `bytes ${start}-${end}/${total}`);
+
+
+                      let stream = fs.createReadStream(path, {
+                          start,
+                          end
+                      });
+                      this.res.writeHead(206);
+                      stream.pipe(this.res);
+                      return
+                }else{
+                    let stream = fs.createReadStream(path);
+                    this.res.setHeader("Content-Type", "video/mp4");
+                    this.res.writeHead(200);
+                    stream.pipe(this.res);
+                    return;
+                }
+
+            } catch(err){
+                this.internalError();
+                return
+            }
+            
+        }else{
+            this.notFound();
+            return
+        }
+    }
+
+    getHandler(){
+        if(this.url.pathname.startsWith("/mp4/")){
+            return this.getMP4();
+        }else if(this.url.pathname == "/"){
+            return this.getPlayerIndex();
+        }else{
+            return this.notFound();
+        }
+    }
+
+    parse(){
+        if(this.req.method == "GET"){
+            return this.getHandler();
+        }else{
+            return this.notFound();
+        }
+    }
+
+}
+
+export class HTTPServer {
+    private port: number;
+    private server?: http.Server;
+
+    private closing: boolean;
+
+    constructor(){
+        this.port = window.settings.options.httpport || 8050;
+        this.closing = false;
+    }
+
+    setPort(port: number){
+        if(this.server && this.server.listening){
+            if(!this.closing){
+                this.closing = true;
+                this.server.close(()=>{
+                    this.port = port;
+                    this.closing = false;
+                    this.listen();
+                })
+            }
+        }else{
+            this.port = port;
+        }
+    }
+
+    create(){
+        this.server = http.createServer((req, res) =>{
+            let request = new HTTPRequest(req, res);
+            request.parse();
+        });
+    }
+
+    listen(){
+        if(this.server){
+            this.server.listen(this.port)
+        }else{
+            throw new Error("Listen before create")
+        }
+    }
+
+}
+
+export default HTTPServer;

+ 39 - 19
app/src/web/Settings.ts

@@ -2,7 +2,6 @@ import { writeFileSync, readFileSync, existsSync, fstat, copyFileSync } from "fs
 import { resolve } from "path";
 import { isArray } from "util";
 import electron, { ipcMain, ipcRenderer } from "electron";
-import { throws } from "assert";
 
 function isSameKeys(o: Object, keys: Set<string>){
     let objectkeys = Object.keys(o);
@@ -27,7 +26,8 @@ const AlertKeys: Set<string> = new Set([
 export type SettingsData = {
     twitch_client_id: string,
     twitch_oauth_token: string,
-    port: number,
+    wsport: number,
+    httpport: number,
     channel: string,
     alerts: {
         [key: string]: Alert
@@ -35,7 +35,8 @@ export type SettingsData = {
 }
 
 const keys: Set<string> = new Set([
-    "port",
+    "wsport",
+    "httpport",
     "twitch_client_id",
     "twitch_oauth_token",
     "channel",
@@ -46,7 +47,8 @@ class Settings {
     options: SettingsData = {
         twitch_client_id: "",
         twitch_oauth_token: "",
-        port: 8045,
+        wsport: 8045,
+        httpport: 8050,
         channel: "",
         alerts: {}
     };
@@ -55,7 +57,8 @@ class Settings {
     private inputs: {
         ClientID?:HTMLInputElement,
         OAuthToken?:HTMLInputElement,
-        Port?:HTMLInputElement,
+        WSPort?:HTMLInputElement,
+        HTTPPort?:HTMLInputElement,
         Channel?:HTMLInputElement,
         SaveButton?:HTMLButtonElement,
         AlertButton?:HTMLButtonElement
@@ -124,14 +127,16 @@ class Settings {
     bind(
         ClientID:HTMLInputElement,
         OAuthToken:HTMLInputElement,
-        Port:HTMLInputElement,
+        WSPort:HTMLInputElement,
+        HTTPPort:HTMLInputElement,
         Channel:HTMLInputElement,
         SaveButton:HTMLButtonElement,
         AlertButton:HTMLButtonElement
     ){
         this.inputs.ClientID = ClientID;
         this.inputs.OAuthToken = OAuthToken;
-        this.inputs.Port = Port;
+        this.inputs.WSPort = WSPort;
+        this.inputs.HTTPPort = HTTPPort;
         this.inputs.Channel = Channel;
         this.inputs.SaveButton = SaveButton;
         this.inputs.AlertButton = AlertButton;
@@ -143,14 +148,28 @@ class Settings {
             this.options.twitch_client_id = this.inputs.ClientID.value;
             this.options.channel = this.inputs.Channel.value;
     
-            // Port
-            let port: number = parseInt(this.inputs.Port.value);
-            if(!isNaN(port) && port > 0 && port < 65535){
-                this.options.port = port;
-                this.inputs.Port.value = port.toString();
+            // WSPort
+            let wsport: number = parseInt(this.inputs.WSPort.value);
+            if(!isNaN(wsport) && wsport > 0 && wsport < 65535){
+                this.options.wsport = wsport;
+                this.inputs.WSPort.value = wsport.toString();
             }else{
-                console.log("Wrong value for port");
-                this.inputs.Port.value = this.options.port.toString();
+                console.log("Wrong value for wsport");
+                this.inputs.WSPort.value = this.options.wsport.toString();
+                // TODO: Some error box
+            }
+
+            // HTTP Port
+            let httpport: number = parseInt(this.inputs.HTTPPort.value);
+            if(!isNaN(httpport) && httpport > 0 && httpport < 65535){
+                if(this.options.httpport != httpport){
+                    window.httpserver.setPort(httpport);
+                }
+                this.options.httpport = httpport;
+                this.inputs.HTTPPort.value = httpport.toString();
+            }else{
+                console.log("Wrong value for httpport");
+                this.inputs.HTTPPort.value = this.options.httpport.toString();
                 // TODO: Some error box
             }
     
@@ -168,13 +187,13 @@ class Settings {
             this.options.channel = this.inputs.Channel.value;
       
             // Port
-            let port: number = parseInt(this.inputs.Port.value);
+            let port: number = parseInt(this.inputs.WSPort.value);
             if(!isNaN(port) && port > 0 && port < 65535){
-                this.options.port = port;
-                this.inputs.Port.value = port.toString();
+                this.options.wsport = port;
+                this.inputs.WSPort.value = port.toString();
             }else{
                 console.log("Wrong value for port");
-                this.inputs.Port.value = this.options.port.toString();
+                this.inputs.WSPort.value = this.options.wsport.toString();
                 // TODO: Some error box
             }
       
@@ -189,7 +208,8 @@ class Settings {
 
     updateView(){
         this.inputs.ClientID.value = this.options.twitch_client_id
-        this.inputs.Port.value = this.options.port.toString()
+        this.inputs.WSPort.value = this.options.wsport.toString()
+        this.inputs.HTTPPort.value = this.options.httpport.toString()
         this.inputs.OAuthToken.value = this.options.twitch_oauth_token;
         this.inputs.Channel.value = this.options.channel;
     }

+ 2 - 2
app/src/web/WSServer.ts

@@ -51,7 +51,7 @@ class WebSocketServer extends EventEmitter {
             console.log("Trying to start websocket when it is already running");
             return
         }
-        this.changePort(window.settings.options.port);
+        this.changePort(window.settings.options.wsport);
         this.server = new ws.Server({
             port: this.port,
             clientTracking: true
@@ -121,7 +121,7 @@ class WebSocketServer extends EventEmitter {
           if(this.status()){
             this.stop();
           }else{
-            this.changePort(window.settings.options.port)
+            this.changePort(window.settings.options.wsport)
             this.start();
           }
         })

+ 3 - 1
app/src/web/global.d.ts

@@ -1,8 +1,10 @@
 import fs from "fs"
 import Settings from "./Settings"
+import HTTPServer from "./HTTPServer"
 declare global {
     interface Window {
         fs: typeof fs,
-        settings: Settings
+        settings: Settings,
+        httpserver: HTTPServer
     }
 }

+ 10 - 3
app/src/web/preload.ts

@@ -5,6 +5,8 @@ import WebSocketServer from "./WSServer";
 import AlertFront from "./AlertFront";
 import Queue, { AlertElement } from "./AlertQueue";
 import generateID from "./utils/generateid";
+import HTTPServer from "./HTTPServer";
+import { KeyObject } from "crypto";
 
 let settings = window.settings = new Settings()
 
@@ -13,6 +15,9 @@ const wss = new WebSocketServer();
 
 wss.start();
 
+const httpserver = window.httpserver =  new HTTPServer();
+httpserver.create();
+httpserver.listen();
 
 const AlertQueue = new Queue<AlertElement>();
 
@@ -31,13 +36,13 @@ function sendPath(path: string){
 wss.on("waitingForVideo", ()=>{
   if(AlertQueue.len() > 0){
     let el = AlertQueue.remove();
-    sendPath(el.filepath);
+    sendPath(`/mp4/${el.alertid}`);
   }
 })
 wss.on("ended", ()=>{
   if(AlertQueue.len() > 0){
     let el = AlertQueue.remove();
-    sendPath(el.filepath);
+    sendPath(`/mp4/${el.alertid}`);
   }
 })
 
@@ -64,7 +69,8 @@ window.addEventListener("DOMContentLoaded", () => {
   settings.bind(
     document.getElementById("settClientToken") as HTMLInputElement,
     document.getElementById("settOAuthToken") as HTMLInputElement,
-    document.getElementById("settPort") as HTMLInputElement,
+    document.getElementById("settWSPort") as HTMLInputElement,
+    document.getElementById("settHTTPPort") as HTMLInputElement,
     document.getElementById("settChannel") as HTMLInputElement,
     document.getElementById("settSave") as HTMLButtonElement,
     document.getElementById("saveAlert") as HTMLButtonElement
@@ -109,6 +115,7 @@ window.addEventListener("DOMContentLoaded", () => {
           let newid = generateID(12);
           AlertQueue.add({
             filepath: al[key].filepath,
+            alertid: key,
             id: newid,
             rewardEvent: reward
           })

+ 7 - 4
app/web/index.html

@@ -85,8 +85,12 @@
                         <input type="text" class="form-control" id="settChannel">
                     </div>
                     <div class="form-group col-12">
-                        <label for="settPort">Port</label>
-                        <input type="text" class="form-control" id="settPort">
+                        <label for="settWSPort">WS Port</label>
+                        <input type="text" class="form-control" id="settWSPort">
+                    </div>
+                    <div class="form-group col-12">
+                        <label for="settHTTPPort">HTTP Port</label>
+                        <input type="text" class="form-control" id="settHTTPPort">
                     </div>
                 </div>
                 <div class="form-group">
@@ -143,7 +147,6 @@
             return false;
         })
 
-        console.log(isSettingsHidden);
         if(!isSettingsHidden){
             col2.addClass("show");
         }
@@ -167,6 +170,6 @@
     </script>
     <script src="lib/popper.min.js"></script>
     <script src="lib/bootstrap.min.js"></script>
-    <script src="../dst/web/main.js"></script>
+    <!-- <script src="../dst/web/main.js"></script> -->
 </body>
 </html>