package main import ( "fmt" "io" "log" "net/http" "path" "path/filepath" "strings" "github.com/zorchenhimer/MovieNight/common" "github.com/gorilla/websocket" "github.com/nareix/joy4/av/avutil" "github.com/nareix/joy4/av/pubsub" "github.com/nareix/joy4/format/flv" "github.com/nareix/joy4/format/rtmp" ) var ( // Read/Write mutex for rtmp stream l = NewSuperLock() // Map of active streams channels = map[string]*Channel{} ) type Channel struct { que *pubsub.Queue } type writeFlusher struct { httpflusher http.Flusher io.Writer } func (self writeFlusher) Flush() error { self.httpflusher.Flush() return nil } // Serving static files func wsStaticFiles(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/favicon.ico": http.ServeFile(w, r, "./favicon.png") return case "/justvideo": http.ServeFile(w, r, "./static/justvideo.html") return } goodPath := r.URL.Path[8:len(r.URL.Path)] common.LogDebugf("[static] serving %q from folder ./static/\n", goodPath) http.ServeFile(w, r, "./static/"+goodPath) } func wsWasmFile(w http.ResponseWriter, r *http.Request) { if settings.NoCache { w.Header().Set("Cache-Control", "no-cache, must-revalidate") } common.LogDebugln("[static] serving wasm file") http.ServeFile(w, r, "./static/main.wasm") } func wsImages(w http.ResponseWriter, r *http.Request) { base := filepath.Base(r.URL.Path) common.LogDebugln("[img] ", base) http.ServeFile(w, r, "./static/img/"+base) } func wsEmotes(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, path.Join("./static/", r.URL.Path)) } // Handling the websocket var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, //not checking origin } //this is also the handler for joining to the chat func wsHandler(w http.ResponseWriter, r *http.Request) { log.Println("ws handler") conn, err := upgrader.Upgrade(w, r, nil) if err != nil { common.LogErrorln("Error upgrading to websocket:", err) return } common.LogDebugln("Connection has been upgraded to websocket") go func() { // Handle incomming messages for { var data common.ClientData err := conn.ReadJSON(&data) if err != nil { //if error then assuming that the connection is closed return } } }() } // returns if it's OK to proceed func checkRoomAccess(w http.ResponseWriter, r *http.Request) bool { session, err := sstore.Get(r, "moviesession") if err != nil { // Don't return as server error here, just make a new session. common.LogErrorf("Unable to get session for client %s: %v\n", r.RemoteAddr, err) } if settings.RoomAccess == AccessPin { pin := session.Values["pin"] // No pin found in session if pin == nil || len(pin.(string)) == 0 { if r.Method == "POST" { // Check for correct pin err = r.ParseForm() if err != nil { common.LogErrorf("Error parsing form") http.Error(w, "Unable to get session data", http.StatusInternalServerError) } postPin := strings.TrimSpace(r.Form.Get("txtInput")) common.LogDebugf("Received pin: %s\n", postPin) if postPin == settings.RoomAccessPin { // Pin is correct. Save it to session and return true. session.Values["pin"] = settings.RoomAccessPin session.Save(r, w) return true } // Pin is incorrect. handlePinTemplate(w, r, "Incorrect PIN") return false } // nope. display pin entry and return handlePinTemplate(w, r, "") return false } // Pin found in session, but it has changed since last time. if pin.(string) != settings.RoomAccessPin { // Clear out the old pin. session.Values["pin"] = nil session.Save(r, w) // Prompt for new one. handlePinTemplate(w, r, "Pin has changed. Enter new PIN.") return false } // Correct pin found in session return true } // TODO: this. if settings.RoomAccess == AccessRequest { http.Error(w, "Requesting access not implemented yet", http.StatusNotImplemented) return false } // Room is open. return true } func handlePinTemplate(w http.ResponseWriter, r *http.Request, errorMessage string) { log.Println("handle pin temp") type Data struct { Title string SubmitText string Notice string } if errorMessage == "" { errorMessage = "Please enter the PIN" } data := Data{ Title: "Enter Pin", SubmitText: "Submit Pin", Notice: errorMessage, } err := common.ExecuteServerTemplate(w, "pin", data) if err != nil { common.LogErrorf("Error executing file, %v", err) } } func handleHelpTemplate(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) } func handleEmoteTemplate(w http.ResponseWriter, r *http.Request) { log.Println("handle emote temp") type Data struct { Title string Emotes map[string]string } data := Data{ Title: "Available Emotes", Emotes: common.Emotes, } err := common.ExecuteServerTemplate(w, "emotes", data) if err != nil { common.LogErrorf("Error executing file, %v", err) } } func handlePin(w http.ResponseWriter, r *http.Request) { log.Println("handle pin") session, err := sstore.Get(r, "moviesession") if err != nil { common.LogDebugf("Unable to get session: %v\n", err) } val := session.Values["pin"] if val == nil { session.Values["pin"] = "1234" err := session.Save(r, w) if err != nil { fmt.Fprintf(w, "unable to save session: %v", err) } fmt.Fprint(w, "Pin was not set") common.LogDebugln("pin was not set") } else { fmt.Fprintf(w, "pin set: %v", val) common.LogDebugf("pin is set: %v\n", val) } } func handleIndexTemplate(w http.ResponseWriter, r *http.Request) { log.Println("handle ind temp") if settings.RoomAccess != AccessOpen { if !checkRoomAccess(w, r) { common.LogDebugln("Denied access") return } common.LogDebugln("Granted access") } type Data struct { Video, Chat bool MessageHistoryCount int Title string } data := Data{ Video: true, Chat: true, MessageHistoryCount: settings.MaxMessageCount, Title: "Movie Night!", } path := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/") if path[0] == "video" { data.Chat = false data.Title += " - video" } // Force browser to replace cache since file was not changed if settings.NoCache { w.Header().Set("Cache-Control", "no-cache, must-revalidate") } err := common.ExecuteServerTemplate(w, "main", data) if err != nil { common.LogErrorf("Error executing file, %v", err) } } func handlePublish(conn *rtmp.Conn) { log.Println("handle publish") streams, _ := conn.Streams() l.Lock() common.LogDebugln("request string->", conn.URL.RequestURI()) urlParts := strings.Split(strings.Trim(conn.URL.RequestURI(), "/"), "/") common.LogDebugln("urlParts->", urlParts) if len(urlParts) > 2 { common.LogErrorln("Extra garbage after stream key") return } /* if len(urlParts) != 2 { common.LogErrorln("Missing stream key") return } if urlParts[1] != settings.GetStreamKey() { common.LogErrorln("Stream key is incorrect. Denying stream.") return //If key not match, deny stream } */ streamPath := urlParts[0] ch := channels[streamPath] if ch == nil { ch = &Channel{} ch.que = pubsub.NewQueue() ch.que.WriteHeader(streams) channels[streamPath] = ch } else { ch = nil } l.Unlock() if ch == nil { common.LogErrorln("Unable to start stream, channel is nil.") return } stats.startStream() common.LogInfoln("Stream started") avutil.CopyPackets(ch.que, conn) common.LogInfoln("Stream finished") stats.endStream() l.Lock() delete(channels, streamPath) l.Unlock() ch.que.Close() } func handlePlay(conn *rtmp.Conn) { log.Println("handle play") l.RLock() ch := channels[conn.URL.Path] l.RUnlock() if ch != nil { cursor := ch.que.Latest() avutil.CopyFile(conn, cursor) } } func handleDefault(w http.ResponseWriter, r *http.Request) { log.Println("handle def") l.RLock() ch := channels[strings.Trim(r.URL.Path, "/")] l.RUnlock() if ch != nil { l.StartStream() defer l.StopStream() w.Header().Set("Content-Type", "video/x-flv") w.Header().Set("Transfer-Encoding", "chunked") w.Header().Set("Access-Control-Allow-Origin", "*") w.WriteHeader(200) flusher := w.(http.Flusher) flusher.Flush() muxer := flv.NewMuxerWriteFlusher(writeFlusher{httpflusher: flusher, Writer: w}) cursor := ch.que.Latest() avutil.CopyFile(muxer, cursor) } else { if r.URL.Path != "/" { // not really an error for the server, but for the client. common.LogInfoln("[http 404] ", r.URL.Path) http.NotFound(w, r) } else { handleIndexTemplate(w, r) } } }