Skip to content

Commit

Permalink
hls client: support EXT-X-BYTERANGE
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Oct 21, 2022
1 parent 71a12ef commit bd44310
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 54 deletions.
24 changes: 23 additions & 1 deletion internal/hls/client_downloader_primary.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ func clientDownloadPlaylist(ctx context.Context, httpClient *http.Client, ur *ur
return m3u8.Unmarshal(byts)
}

func allCodecsAreSupported(codecs string) bool {
for _, codec := range strings.Split(codecs, ",") {
if !strings.HasPrefix(codec, "avc1") &&
!strings.HasPrefix(codec, "mp4a") {
return false
}
}
return true
}

type clientDownloaderPrimary struct {
primaryPlaylistURL *url.URL
logger ClientLogger
Expand Down Expand Up @@ -159,9 +169,21 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
streamCount++

case *m3u8.MasterPlaylist:
// gather variants with supported codecs
var supportedVariants []*gm3u8.Variant
for _, v := range plt.Variants {
if !allCodecsAreSupported(v.Codecs) {
continue
}
supportedVariants = append(supportedVariants, v)
}
if supportedVariants == nil {
return fmt.Errorf("no variants with supported codecs found")
}

// choose the variant with the greatest bandwidth
var chosenVariant *gm3u8.Variant
for _, v := range plt.Variants {
for _, v := range supportedVariants {
if chosenVariant == nil ||
v.VariantParams.Bandwidth > chosenVariant.VariantParams.Bandwidth {
chosenVariant = v
Expand Down
17 changes: 12 additions & 5 deletions internal/hls/client_downloader_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"time"

"github.com/aler9/gortsplib"
Expand Down Expand Up @@ -64,7 +65,7 @@ func (d *clientDownloaderStream) run(ctx context.Context) error {
}

if initialPlaylist.Map != nil && initialPlaylist.Map.URI != "" {
byts, err := d.downloadSegment(ctx, initialPlaylist.Map.URI)
byts, err := d.downloadSegment(ctx, initialPlaylist.Map.URI, initialPlaylist.Map.Offset, initialPlaylist.Map.Limit)
if err != nil {
return err
}
Expand Down Expand Up @@ -125,8 +126,10 @@ func (d *clientDownloaderStream) downloadPlaylist(ctx context.Context) (*m3u8.Me
return plt, nil
}

func (d *clientDownloaderStream) downloadSegment(ctx context.Context, segmentURI string) ([]byte, error) {
u, err := clientAbsoluteURL(d.playlistURL, segmentURI)
func (d *clientDownloaderStream) downloadSegment(ctx context.Context,
uri string, offset int64, limit int64,
) ([]byte, error) {
u, err := clientAbsoluteURL(d.playlistURL, uri)
if err != nil {
return nil, err
}
Expand All @@ -137,13 +140,17 @@ func (d *clientDownloaderStream) downloadSegment(ctx context.Context, segmentURI
return nil, err
}

if limit != 0 {
req.Header.Add("Range", "bytes="+strconv.FormatInt(offset, 10)+"-"+strconv.FormatInt(offset+limit-1, 10))
}

res, err := d.httpClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusPartialContent {
return nil, fmt.Errorf("bad status code: %d", res.StatusCode)
}

Expand Down Expand Up @@ -194,7 +201,7 @@ func (d *clientDownloaderStream) fillSegmentQueue(ctx context.Context) error {
v := seg.SeqId
d.curSegmentID = &v

byts, err := d.downloadSegment(ctx, seg.URI)
byts, err := d.downloadSegment(ctx, seg.URI, seg.Offset, seg.Limit)
if err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions internal/hls/fmp4/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ func (i *Init) Unmarshal(byts []byte) error {
IndexDeltaLength: 3,
}
state = waitingTrak

case "ac-3":
return nil, fmt.Errorf("AC-3 codec is not supported (yet)")
}

return h.Expand()
Expand Down
48 changes: 24 additions & 24 deletions internal/hls/fmp4/init_track.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,30 @@ type InitTrack struct {

func (track *InitTrack) marshal(w *mp4Writer) error {
/*
trak
- tkhd
- mdia
- mdhd
- hdlr
- minf
- vmhd (video only)
- smhd (audio only)
- dinf
- dref
- url
- stbl
- stsd
- avc1 (h264 only)
- avcC
- pasp
- btrt
- mp4a (mpeg4audio only)
- esds
- btrt
- stts
- stsc
- stsz
- stco
trak
- tkhd
- mdia
- mdhd
- hdlr
- minf
- vmhd (video only)
- smhd (audio only)
- dinf
- dref
- url
- stbl
- stsd
- avc1 (h264 only)
- avcC
- pasp
- btrt
- mp4a (mpeg4audio only)
- esds
- btrt
- stts
- stsc
- stsz
- stco
*/

_, err := w.writeBoxStart(&gomp4.Trak{}) // <trak>
Expand Down
67 changes: 43 additions & 24 deletions internal/hls/fmp4/part.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fmp4

import (
"bytes"
"errors"
"fmt"

gomp4 "github.com/abema/go-mp4"
Expand All @@ -15,6 +16,8 @@ const (
trunFlagSampleCompositionTimeOffsetPresentOrV1 = 0x800
)

var errAllRight = errors.New("all right")

// Part is a FMP4 part file.
type Part struct {
Tracks []*PartTrack
Expand All @@ -30,9 +33,7 @@ func (ps *Parts) Unmarshal(byts []byte) error {
const (
waitingMoof readState = iota
waitingTraf
waitingTfhd
waitingTfdt
waitingTrun
waitingTfdtTfhdTrun
)

state := waitingMoof
Expand All @@ -42,12 +43,14 @@ func (ps *Parts) Unmarshal(byts []byte) error {
var defaultSampleDuration uint32
var defaultSampleFlags uint32
var defaultSampleSize uint32
tfdtFound := false
tfhdFound := false

_, err := gomp4.ReadBoxStructure(bytes.NewReader(byts), func(h *gomp4.ReadHandle) (interface{}, error) {
switch h.BoxInfo.Type.String() {
case "moof":
if state != waitingMoof {
return nil, fmt.Errorf("decode error")
return nil, fmt.Errorf("unexpected moof")
}

curPart = &Part{}
Expand All @@ -56,17 +59,25 @@ func (ps *Parts) Unmarshal(byts []byte) error {
state = waitingTraf

case "traf":
if state != waitingTraf {
return nil, fmt.Errorf("decode error")
if state != waitingTraf && state != waitingTfdtTfhdTrun {
return nil, fmt.Errorf("unexpected traf")
}

if curTrack != nil {
if !tfdtFound || !tfhdFound || curTrack.Samples == nil {
return nil, fmt.Errorf("parse error")
}
}

curTrack = &PartTrack{}
curPart.Tracks = append(curPart.Tracks, curTrack)
state = waitingTfhd
tfdtFound = false
tfhdFound = false
state = waitingTfdtTfhdTrun

case "tfhd":
if state != waitingTfhd {
return nil, fmt.Errorf("decode error")
if state != waitingTfdtTfhdTrun || tfhdFound {
return nil, fmt.Errorf("unexpected tfhd")
}

box, _, err := h.ReadPayload()
Expand All @@ -76,15 +87,14 @@ func (ps *Parts) Unmarshal(byts []byte) error {
tfhd := box.(*gomp4.Tfhd)

curTrack.ID = int(tfhd.TrackID)

defaultSampleDuration = tfhd.DefaultSampleDuration
defaultSampleFlags = tfhd.DefaultSampleFlags
defaultSampleSize = tfhd.DefaultSampleSize
state = waitingTfdt
tfhdFound = true

case "tfdt":
if state != waitingTfdt {
return nil, fmt.Errorf("decode error")
if state != waitingTfdtTfhdTrun || tfdtFound {
return nil, fmt.Errorf("unexpected tfdt")
}

box, _, err := h.ReadPayload()
Expand All @@ -98,11 +108,11 @@ func (ps *Parts) Unmarshal(byts []byte) error {
}

curTrack.BaseTime = tfdt.BaseMediaDecodeTimeV1
state = waitingTrun
tfdtFound = true

case "trun":
if state != waitingTrun {
return nil, fmt.Errorf("decode error")
if state != waitingTfdtTfhdTrun {
return nil, fmt.Errorf("unexpected trun")
}

box, _, err := h.ReadPayload()
Expand All @@ -116,7 +126,11 @@ func (ps *Parts) Unmarshal(byts []byte) error {
return nil, fmt.Errorf("unsupported flags")
}

curTrack.Samples = make([]*PartSample, len(trun.Entries))
existing := len(curTrack.Samples)
tmp := make([]*PartSample, existing+len(trun.Entries))
copy(tmp, curTrack.Samples)
curTrack.Samples = tmp

ptr := byts[uint64(trun.DataOffset)+moofOffset:]

for i, e := range trun.Entries {
Expand Down Expand Up @@ -146,22 +160,27 @@ func (ps *Parts) Unmarshal(byts []byte) error {
s.Payload = ptr[:size]
ptr = ptr[size:]

curTrack.Samples[i] = s
curTrack.Samples[existing+i] = s
}

state = waitingTraf

case "mdat":
if state != waitingTraf {
return nil, fmt.Errorf("decode error")
if state != waitingTraf && state != waitingTfdtTfhdTrun {
return nil, fmt.Errorf("unexpected mdat")
}

if curTrack != nil {
if !tfdtFound || !tfhdFound || curTrack.Samples == nil {
return nil, fmt.Errorf("parse error")
}
}

state = waitingMoof
return nil, nil
return nil, errAllRight
}

return h.Expand()
})
if err != nil {
if err != errAllRight {
return err
}

Expand Down

0 comments on commit bd44310

Please sign in to comment.