func AddSegmentToSchedule(s *datastore.Schedule, bs BroadcastSegment) error { playlistBlob := datastore.Blob{s.CacheKey()} if s.MediaPlaylist == nil { // If no playlist exists s.MediaPlaylist, _ = m3u8.NewMediaPlaylist(100, 100) // alloc a new one if plistData, err := playlistBlob.GetBuffer(); err == nil { s.MediaPlaylist.Decode(*plistData, false) } } err := s.MediaPlaylist.Append(bs.MediaURL, bs.TargetDuration, bs.Title) // append the item if err != nil { s.MediaPlaylist.Slide(bs.MediaURL, bs.TargetDuration, bs.Title) // or slide if playlist full } s.MediaPlaylist.SetProgramDateTime(time.Now()) if bs.Discontinuity { // If the video format is different than the last segment if err := s.MediaPlaylist.SetDiscontinuity(); err != nil { // let the player know return err } } if err := playlistBlob.Set(s.PlaylistData()); err != nil { return err } return nil }
func HandleDeleteProgram(s *datastore.Schedule, w http.ResponseWriter, r *http.Request) { urlComps := strings.Split(r.URL.Path, "/") if len(urlComps) < 2 { http.Error(w, "Cannot delete schedule", 400) return } programId := urlComps[1] programs, err := s.ListPrograms(1) if err != nil { http.Error(w, "Error finding programs", 500) return } for i := range programs { program := programs[i] if program.UUID == programId { err := s.DeleteProgram(program) if err != nil { http.Error(w, "Error deleting program", 500) } return } } http.Error(w, "Program not found", 404) }
func Start(s *datastore.Schedule) { log.Println("Checking if ready to play", s.Id, "channel") if ok := s.ReadyToPlay(); !ok { log.Println("Not ready to play") return } for err := runCurrentProgram(s); err == nil; err = runCurrentProgram(s) { } }
func HandleListPrograms(s *datastore.Schedule, w http.ResponseWriter, r *http.Request) { programs, err := s.ListPrograms(1) if err != nil { http.Error(w, "Error retrieving schedule for channel", 500) return } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(programs) }
func HandleAddProgram(s *datastore.Schedule, w http.ResponseWriter, r *http.Request) { dec := json.NewDecoder(r.Body) if _, err := dec.Token(); err != nil { http.Error(w, "Error parsing program", 400) return } var p datastore.Program for dec.More() { if err := dec.Decode(&p); err != nil { http.Error(w, "Error parsing program instance", 400) return } s.AddProgram(p) } HandleListPrograms(s, w, r) }
func runCurrentProgram(s *datastore.Schedule) error { cp, err := s.CurrentProgram() if err != nil { log.Println("Error fetching current program:", s.Id, err) return err } // Rewrite the start_at parameter to resume position from playback counter params := cp.Parameters pc := cp.PlaybackCounter(s.Id) startAt, _ := params["start_at"] startParam, parseErr := strconv.ParseInt(startAt, 10, 64) if parseErr != nil { startParam = 0 } newStart := startParam + pc params["start_at"] = fmt.Sprintf("%d", newStart) // now we run the function pipeline := RunPipe(cp.FunctionId, cp.UUID, params) checkTicker := time.Tick(100 * time.Millisecond) pipeline.Close() cancelStillPlaying := make(chan int, 3) go func() { timeout := time.Tick(1 * time.Second) s.StillPlaying() // lock schedule playback for { select { case <-cancelStillPlaying: return case <-timeout: s.StillPlaying() } } }() cancelPlayingMarker := func() { cancelStillPlaying <- 0 } defer cancelPlayingMarker() startDiscontinuityWritten := false // always write discontinuity when starting segmentTimer := time.After(1 * time.Nanosecond) for pipeout := pipeline.MediaChannel(); true; { select { case <-segmentTimer: // if it's time to send the next segment v, more := <-pipeout segment := v.Segment segmentDuration := time.Duration(segment.TargetDuration * 1000000) segmentTimer = time.After(segmentDuration * time.Microsecond) cp.IncrementPlaybackCounter(s.Id) if !more { cp.ResetPlaybackCounter(s.Id) return nil } if !startDiscontinuityWritten { segment.Discontinuity = true startDiscontinuityWritten = true } if err := AddSegmentToSchedule(s, segment); err != nil { log.Println("Error adding new segment to schedule:", err) } case <-checkTicker: // if it's time to check for the next program nextProgramStart, err := s.NextProgramStart() if err == nil && time.Now().After(nextProgramStart) { s.PopCurrentProgram() cp.ResetPlaybackCounter(s.Id) return nil } } } return nil }