streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

hlsproxy.go (2187B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"log"
      6 	"net/http"
      7 	"net/url"
      8 	"os"
      9 	"path"
     10 	"time"
     11 
     12 	"github.com/untangledco/streaming/m3u8"
     13 )
     14 
     15 const usage string = "usage: hlsproxy url"
     16 
     17 func init() {
     18 	log.SetFlags(0)
     19 	log.SetPrefix("hlssrv: ")
     20 }
     21 
     22 func servePlaylist(p *m3u8.Playlist) http.HandlerFunc {
     23 	return func(w http.ResponseWriter, req *http.Request) {
     24 		if req.Method != http.MethodGet {
     25 			http.Error(w, "nope", http.StatusMethodNotAllowed)
     26 		}
     27 		log.Println(req.Method, req.URL)
     28 		w.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
     29 		if err := m3u8.Encode(w, p); err != nil {
     30 			log.Println("encode playlist:", err)
     31 		}
     32 	}
     33 }
     34 
     35 func injectBreak(plist *m3u8.Playlist, dur time.Duration, schedule int) error {
     36 	// ticks := (dur * time.Second) / 90000
     37 	var money []m3u8.Segment
     38 	for i, seg := range plist.Segments {
     39 		if i%schedule != 0 || i == 0 {
     40 			money = append(money, seg)
     41 			continue
     42 		}
     43 		adbreak := m3u8.Segment{
     44 			URI:           "https://test-streams.mux.dev/test_001/stream_1000k_48k_640x360_000.ts",
     45 			Duration:      dur,
     46 			Discontinuity: true,
     47 		}
     48 		seg.Discontinuity = true
     49 		money = append(money, adbreak, seg)
     50 	}
     51 	plist.Segments = money
     52 	return nil
     53 }
     54 
     55 func main() {
     56 	if len(os.Args) != 2 {
     57 		fmt.Fprintln(os.Stderr, usage)
     58 		os.Exit(127)
     59 	}
     60 	link, err := url.Parse(os.Args[1])
     61 	if err != nil {
     62 		log.Fatal(err)
     63 	}
     64 	resp, err := http.Get(link.String())
     65 	if err != nil {
     66 		log.Fatal("get playlist:", err)
     67 	}
     68 	defer resp.Body.Close()
     69 	if resp.StatusCode != http.StatusOK {
     70 		log.Fatal("get playlist: remote status:", resp.Status)
     71 	}
     72 	source, err := m3u8.Decode(resp.Body)
     73 	if err != nil {
     74 		log.Fatal("parse playlist:", err)
     75 	}
     76 	link.Path = path.Dir(link.Path) + "/"
     77 	for i := range source.Media {
     78 		source.Media[i].URI = link.String() + source.Media[i].URI
     79 	}
     80 	for i := range source.Variants {
     81 		source.Variants[i].URI = link.String() + source.Variants[i].URI
     82 	}
     83 	for i := range source.Segments {
     84 		source.Segments[i].URI = link.String() + source.Segments[i].URI
     85 	}
     86 	if err := injectBreak(source, 10*time.Second, 8); err != nil {
     87 		log.Fatalf("inject ad break: %v", err)
     88 	}
     89 	http.HandleFunc("/", servePlaylist(source))
     90 	log.Fatal(http.ListenAndServe(":8080", nil))
     91 }