Add handlers.
This commit is contained in:
parent
523d313642
commit
7b49753570
@ -19,7 +19,8 @@ func main() {
|
||||
//http.HandleFunc("GET /api/v1/vods/{id}/", handlers.ConfigVodsHandler)
|
||||
//http.HandleFunc("DELETE /api/v1/vods/{id}/", handlers.DelVodsHandler)
|
||||
//http.HandleFunc("GET /api/v1/vods/{id}/files/", handlers.ListFilesVodsHandler)
|
||||
//http.HandleFunc("GET /api/v1/vods/{id}/{res}/{file}", handlers.FileVodsHandler)
|
||||
//http.HandleFunc("GET /api/v1/vods/{id}/{res}/{file}", handlers.SingleVodsHandler)
|
||||
//http.HandleFunc("DELETE /api/v1/vods/{id}/{res}/{file}", handlers.DelSingleVodsHandler)
|
||||
//
|
||||
//log.Println("Starting server on:")
|
||||
//log.Printf("Serving on HTTP port: %d\n", port)
|
||||
|
@ -2,8 +2,19 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.insit.tech/psa/rtsp_reader-writer/writer/pkg/storage"
|
||||
"go.uber.org/zap"
|
||||
"reader/internal/config"
|
||||
logger "reader/internal/log"
|
||||
"reader/internal/processor"
|
||||
)
|
||||
|
||||
@ -13,6 +24,87 @@ type VideoRequest struct {
|
||||
EndTime string `json:"end_time"`
|
||||
}
|
||||
|
||||
type ListVodsResponse struct {
|
||||
EstimatedCount int `json:"estimated_count"` // Estimated total number of records for the query.
|
||||
Vods []string `json:"vods"` // List of vods.
|
||||
Files map[string][]string `json:"files"` // List files and folders of a specific VOD location.
|
||||
}
|
||||
|
||||
type ConfigVodsResponse struct {
|
||||
Prefix string `json:"prefix"` // The unique name of VOD location.
|
||||
AutoMbr bool `json:"auto_mbr"` // Turns on automatic creation of a multi-bitrate HLS playlist from several files with different bitrates.
|
||||
Disabled bool `json:"disabled"` // Whether this VOD location is disabled.
|
||||
Protocols struct { // Configuraton of play protocols.
|
||||
Hls bool `json:"hls"` // Whether to allow or deny an HLS stream playback.
|
||||
Cmaf bool `json:"cmaf"` // Whether to allow or deny an LL-HLS stream playback.
|
||||
Dash bool `json:"dash"` // Whether to allow or deny a DASH stream playback.
|
||||
Player bool `json:"player"` // Whether to allow or deny playback in embed.html.
|
||||
Mss bool `json:"mss"` // Whether to allow or deny an MSS stream playback.
|
||||
Rtmp bool `json:"rtmp"` // Whether to allow or deny an RTMP stream playback.
|
||||
Rtsp bool `json:"rtsp"` // Whether to allow or deny an RTSP stream playback.
|
||||
M4F bool `json:"m4f"` // Whether to allow or deny an M4F stream playback.
|
||||
M4S bool `json:"m4s"` // Whether to allow or deny an M4S stream playback.
|
||||
Mseld bool `json:"mseld"` // Whether to allow or deny an MSE-LD stream playback.
|
||||
Tshttp bool `json:"tshttp"` // Whether to allow or deny an MPEG-TS stream playback over HTTP(S).
|
||||
Webrtc bool `json:"webrtc"` // Whether to allow or deny an WebRTC stream playback.
|
||||
Srt bool `json:"srt"` // Whether to allow or deny an SRT stream playback.
|
||||
Shoutcast bool `json:"shoutcast"` // Whether to allow or deny a SHOUTcast/Icecast stream playback.
|
||||
Mp4 bool `json:"mp4"` // Whether to allow or deny an MP4 file download over HTTP(S).
|
||||
Jpeg bool `json:"jpeg"` // Whether to allow or deny delivering JPEG thumbnails over HTTP(S).
|
||||
Api bool `json:"api"` // Whether to allow or deny API requests.
|
||||
} `json:"protocols"`
|
||||
SegmentDuration int `json:"segment_duration"` // The time, in seconds, of the segment duration. Used for the protocols like HLS or DASH.
|
||||
AddAudioOnly bool `json:"add_audio_only"` // Whether to add an audio-only version of an HLS stream. Used to create App Store compliant HLS streams to deliver the content to Apple iOS devices. Add audio-only HLS playlist to variant MBR playlist for iOS compliant streaming.
|
||||
Provider string `json:"provider"` // Human-readable name of the content provider. Applicable to MPEG-TS.
|
||||
}
|
||||
|
||||
// Inactive5Minutes checks if a folder has files that was not created or modified for last 5 minutes.
|
||||
func Inactive5Minutes(entries []os.DirEntry) (bool, error) {
|
||||
threshold := time.Now().Add(-5 * time.Minute)
|
||||
|
||||
disabled := true
|
||||
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return true, errors.New("Info error: " + err.Error())
|
||||
}
|
||||
|
||||
if info.ModTime().After(threshold) {
|
||||
disabled = false
|
||||
return disabled, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// recDurationMilliseconds calculates the difference between first existing rec file and the last one in milliseconds.
|
||||
func recDurationMilliseconds(entries []os.DirEntry) (int, error) {
|
||||
// Find last file and first file; get timestamps.
|
||||
lastFile := entries[len(entries)-1].Name()
|
||||
lastTime := strings.Split(lastFile, "_")[0]
|
||||
|
||||
firstFile := entries[0].Name()
|
||||
firstTime := strings.Split(firstFile, "_")[0]
|
||||
|
||||
// Convert string timestamps to int.
|
||||
lastTimeInt, err := strconv.Atoi(lastTime)
|
||||
if err != nil {
|
||||
return 0, errors.New("convert last time error: " + err.Error())
|
||||
}
|
||||
|
||||
firstTimeInt, err := strconv.Atoi(firstTime)
|
||||
if err != nil {
|
||||
return 0, errors.New("convert first time error: " + err.Error())
|
||||
}
|
||||
|
||||
// Calculate the difference.
|
||||
difference := lastTimeInt - firstTimeInt
|
||||
|
||||
return difference * 1000, nil
|
||||
}
|
||||
|
||||
// Download processes Download request.
|
||||
func Download(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("new download request: %+v\n", r)
|
||||
@ -50,22 +142,240 @@ func HLS(w http.ResponseWriter, r *http.Request) {
|
||||
http.StripPrefix("/hls", http.FileServer(http.Dir(path))).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
//
|
||||
// vod
|
||||
//
|
||||
// GET List VOD locations
|
||||
// GET List files in VOD locations which are played by the clients
|
||||
// GET Get VOD location
|
||||
// PUT Save VOD location
|
||||
// DEL Delete VOD location
|
||||
// GET List files in a VOD location
|
||||
// GET Get a single VOD file
|
||||
// PUT Save a VOD file
|
||||
// DEL Delete a VOD file
|
||||
//
|
||||
|
||||
// List VOD locations
|
||||
// ListVodsHandler returns the list of VOD locations.
|
||||
//
|
||||
// This method allows to get the list of all VOD locations. VOD location is a virtual filepath used to place files for
|
||||
// VOD (Video on Demand) broadcasting.
|
||||
func ListVodsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Read directory.
|
||||
entries, err := os.ReadDir(config.DirData)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("failed to read dir", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Filter only folders.
|
||||
var dirs []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the Response.
|
||||
VodsRes := ListVodsResponse{
|
||||
EstimatedCount: len(dirs),
|
||||
Vods: dirs,
|
||||
}
|
||||
|
||||
// Write header and code response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(VodsRes); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("failed to encode dir", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigVodsHandler returns configuration of the requested VOD location.
|
||||
//
|
||||
// This method allows to get a single VOD location.
|
||||
func ConfigVodsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Read camera id.
|
||||
id := r.PathValue("id")
|
||||
|
||||
// Get resolutions.
|
||||
resolutions, err := storage.GetResolutions(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Log.Error("camera does not exist", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate response fields.
|
||||
|
||||
// Create path to first resolution.
|
||||
resDir := fmt.Sprintf("%s/%s/%s", config.DirData, id, resolutions[0])
|
||||
|
||||
// Read directory.
|
||||
entries, err := os.ReadDir(resDir)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("resolution does not exist", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a folder has files that was not created or modified for last 5 minutes.
|
||||
disabled, err := Inactive5Minutes(entries)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
segmentDuration, err := recDurationMilliseconds(entries)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Prepare the Response.
|
||||
VodsRes := ConfigVodsResponse{
|
||||
Prefix: id,
|
||||
AutoMbr: false, // Always false. Temporary.
|
||||
Disabled: disabled,
|
||||
SegmentDuration: segmentDuration,
|
||||
AddAudioOnly: false, // Always false. Temporary.
|
||||
Provider: "Insit",
|
||||
}
|
||||
|
||||
// Write header and code response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(VodsRes); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("failed to encode dir", zap.Error(err))
|
||||
return
|
||||
}
|
||||
w.Write([]byte("Whole VOD location configuration"))
|
||||
}
|
||||
|
||||
// DelVodsHandler delete archive of the requested VOD location.
|
||||
//
|
||||
// This method delete a single VOD location by its prefix.
|
||||
func DelVodsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Read camera id.
|
||||
id := r.PathValue("id")
|
||||
|
||||
err := os.Remove(fmt.Sprintf("%s/%s", config.DirData, id))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Log.Error("camera does not exist", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.Write([]byte("Deleted"))
|
||||
}
|
||||
|
||||
// ListFilesVodsHandler returns the list of all files and folders in archive for a specific VOD location.
|
||||
//
|
||||
// This method allows to get the list of all files and folders for a specific VOD location.
|
||||
func ListFilesVodsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Read camera id.
|
||||
id := r.PathValue("id")
|
||||
|
||||
// Create map for response.
|
||||
files := make(map[string][]string)
|
||||
|
||||
// Get resolutions.
|
||||
resolutions, err := storage.GetResolutions(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Log.Error("camera does not exist", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, resolution := range resolutions {
|
||||
// Create path to the resolutions.
|
||||
resDir := fmt.Sprintf("%s/%s/%s", config.DirData, id, resolution)
|
||||
|
||||
// Read directory.
|
||||
entries, err := os.ReadDir(resDir)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("resolution does not exist", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create slice for files in folders.
|
||||
filesInFolder := make([]string, 0)
|
||||
|
||||
// Add all files to the slice with files.
|
||||
for _, entry := range entries {
|
||||
filesInFolder = append(filesInFolder, entry.Name())
|
||||
}
|
||||
|
||||
// Add resolution and all files to the response map.
|
||||
files[resolution] = filesInFolder
|
||||
}
|
||||
|
||||
// Prepare the Response.
|
||||
vodsRes := ListVodsResponse{
|
||||
EstimatedCount: len(files),
|
||||
Files: files,
|
||||
}
|
||||
|
||||
// Write header and code response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte("List of files in the VOD storage"))
|
||||
if err := json.NewEncoder(w).Encode(vodsRes); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("failed to encode dir", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// SingleVodsHandler returns a specific file in archive for a specific resolution and VOD location.
|
||||
//
|
||||
// This method allows to get a single VOD file.
|
||||
func SingleVodsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Read camera id, res, filename.
|
||||
id := r.PathValue("id")
|
||||
res := r.PathValue("res")
|
||||
file := r.PathValue("file")
|
||||
|
||||
// Calculate file size in bytes.
|
||||
FileBytes, err := storage.FileBytes(id, res, file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("file not found", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
dur, tracks, err := storage.GetDurAndTracks(id, res, file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("file not found", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
media := MediaSingleVodsResponse{
|
||||
Tracks: tracks,
|
||||
Duration: dur,
|
||||
Provider: "Insit",
|
||||
Title: id,
|
||||
}
|
||||
|
||||
// Prepare the Response.
|
||||
vodsRes := SingleVodsResponse{
|
||||
Name: file,
|
||||
Prefix: id,
|
||||
Url: r.URL.String(),
|
||||
Folder: res,
|
||||
Bytes: FileBytes,
|
||||
MediaInfo: media,
|
||||
}
|
||||
|
||||
// Write header and code response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte("Whole VOD file configuration"))
|
||||
if err := json.NewEncoder(w).Encode(vodsRes); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Log.Error("failed to encode dir", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type SingleVodsResponse struct {
|
||||
Name string `json:"name"`
|
||||
Prefix string `json:"prefix"`
|
||||
Url string `json:"url"`
|
||||
Folder string `json:"folder"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
MediaInfo MediaSingleVodsResponse `json:"media_info"`
|
||||
}
|
||||
|
||||
type MediaSingleVodsResponse struct {
|
||||
Tracks map[string]string `json:"tracks"`
|
||||
Duration int `json:"duration"`
|
||||
Provider string `json:"provider"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user