Giter Club home page Giter Club logo

hls-vodtolive's Introduction

hls-vodtolive

License: MIT Coverage Status Slack

Node library for the conversion of HLS VOD to HLS Live (A continuation and rebranding of vod-to-live.js library that is now deprecated)

Installation

npm install --save @eyevinn/hls-vodtolive

Usage

This library load and parses HLS VOD manifests and generates HLS Live manifests. The example below loads one HLS VOD and then another HLS VOD that is appended to the first one. The getLiveMediaSequences(mediaseq) returns HLS Live media sequence slices, and in the example below outputs the last live media sequence representation of the first VOD.

const HLSVod = require('@eyevinn/hls-vodtolive');
const vod = new HLSVod('https://example.com/vod.m3u8');
const vod2 = new HLSVod('https://example.com/vod2.m3u8');
vod.load().then(() => {
    // Get media sequence no 5 for bitrate 798000
    console.log(vod.getLiveMediaSequences(0, '798000', 5));
    return vod2.loadAfter(vod);
}).then(() => {
    console.log(vod.getLiveMediaSequences(vod.getLiveMediaSequencesCount(), '798000', 0));
}).catch(console.error);

One use case for this library is to simulate a linear live HLS stream by concatenating HLS VODs together which live HLS manifests are generated from. The open source library Channel Engine provides an example of this.

What this library does can be illustrated by this simplified example below:

#EXTINF:9
seg1.ts
#EXTINF:9
seg2.ts
#EXTINF:9
seg3.ts
#EXTINF:4
seg4.ts
#EXT-X-ENDLIST

is made available as:

#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:9
seg1.ts
#EXTINF:9
seg2.ts
#EXTINF:9
seg3.ts
#EXT-X-MEDIA-SEQUENCE:2
#EXTINF:9
seg2.ts
#EXTINF:9
seg3.ts
#EXTINF:4
seg4.ts

Another on-demand HLS can be concatenated

#EXTINF:9
segB1.ts
#EXTINF:9
segB2.ts
#EXTINF:9
segB3.ts
#EXTINF:4
segB4.ts
#EXT-X-ENDLIST

to yield the following media sequences

#EXT-X-MEDIA-SEQUENCE:3
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXTINF:9
seg3.ts
#EXTINF:4
seg4.ts
#EXT-X-DISCONTINUITY
#EXTINF:9
segB1.ts
#EXT-X-MEDIA-SEQUENCE:4
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXTINF:4
seg4.ts
#EXT-X-DISCONTINUITY
#EXTINF:9
segB1.ts
#EXTINF:9
segB2.ts
#EXT-X-MEDIA-SEQUENCE:5
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-DISCONTINUITY
#EXTINF:9
segB1.ts
#EXTINF:9
segB2.ts
#EXTINF:9
segB3.ts

To use this library with subtitles the following options are required to be supplied when creating a new instance of HLSVod

shouldContainSubtitles: true, // says that the loaded VOD should contain subtitles and to create dummy if missing.
expectedSubtitleTracks: subtitleTracks, // says that the loaded VOD should contain subtitles and to create dummy if missing
dummySubtitleEndpoint: "/dummysubs.vtt", // it should link to an endpoint that will serve empty vtt files.
subtitleSliceEndpoint: "/subtitlevtt.vtt", // it should link to an endpoint that can splice and vtt file and serve it in case a VOD contains
   subtitle segments longer than video segments.

Documentation

Authors

This project was started as vod-to-live.js in 2018 by Eyevinn Technology.

Contributors

Attributions

A special thanks to OTTera for funding a number of bugfixes and help to triage issues. OTTera is a US based company that powers OTT and Linear Video services with over 45 million users worldwide.

Contributing

In addition to contributing code, you can help to triage issues. This can include reproducing bug reports, or asking for vital information such as version numbers or reproduction instructions.

License (MIT)

Copyright 2020 Eyevinn Technology

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Support

Join our community on Slack where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:

  • Further development of this component
  • Customization and integration of this component into your platform
  • Support and maintenance agreement

Contact [email protected] if you are interested.

About Eyevinn Technology

Eyevinn Technology is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor.

At Eyevinn, every software developer consultant has a dedicated budget reserved for open source development and contribution to the open source community. This give us room for innovation, team building and personal competence development. And also gives us as a company a way to contribute back to the open source community.

Want to know more about Eyevinn and how it is to work here. Contact us at [email protected]!

hls-vodtolive's People

Contributors

birme avatar craigmc-ottera avatar dependabot[bot] avatar nfrederiksen avatar oscnord avatar sgtfishtank avatar sinewave440hz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hls-vodtolive's Issues

Add Option for forcing at least one new segment on each media-sequence

Currently, when creating the vod to live list of media sequences, the playlist segments are distributed to each sequence based on a target SEQUENCE_DURATION.

However, the sequences are set to only ever shift 1 segment at a time. This means that, in a case where one sequence is 59s long, the top segment is 3s, and the next 'incoming' segment is 6s long. Then it would not suffice to only remove one segment, otherwise, the total sequence duration would land on 64s.
It solves this by only removing one 3s segment, and skipping adding the new 'incoming' segment when making the new sequence.
The 6s segment will be added to a sequence once the total sequence duration has shrunken low enough. e.i. once another 3s segment has been removed.

SUGGESTION:
We should add an input option, that once is set will use logic that ensures that each sequence contains at least 1 new segment. e.i. allow removing more than just one top segment when making the next sequence.

Edge Case - Wrong DeltaTimes in _createMediaSequences(), specifically [ TYPE-A ]

In function _createMediaSequences()

The Delta times calculation (this.deltaTimes, this.deltaTimesAudio) is wrong in the case where 2 or more segments were added in the next sequence.

The var positionIncrement assumes that the last segment in the sequence is the only newly added one. So eg. in the case where a sequence popped a 6s segment and added two 3s segments, currently the second segment duration is not accounted for in the delta times.

It has gone unnoticed since the side effects were not apparent in most use cases. But if you have a case where deltaTimes and playhead positions are important, then this should be fixed.

NOTE: This is issue already fixed when enabling the this.sequenceAlwaysContainNewSegments option [ TYPE-B ]

EXT-X-CUE-IN sometimes incorrectly removed

For a case where you need to process the last seg-Item in a manifest, e.g save it to a List, the following bug will occur.

Consider these 2 media sequences to be parsed after another:
mseq_1

#EXTINF:6
http://ad_seg0.ts
#EXTINF:6
http://ad_seg1.ts
#EXTINF:6
http://ad_seg2.ts

followed by --->
mseq_2

#EXTINF:6
http://ad_seg1.ts
#EXTINF:6
http://ad_seg2.ts
#EXT-X-CUE-IN
#EXTINF:6
http://other_seg1.ts

Using the parser in node-m3u8 v0.4.0 on mseq_1, it would parse the last segment (which doesn't have any CUE-data attached to it) and save it in a Segment List.

Next mseq_2 comes, and now the CUE-data is visible in the manifest. The v0.4.0 parser would parse and create the last 2 seg-Items as such:

[ 
.
.
.
   {
    "attributes": {
      "attributes": {
        "cuein": true
      }
    },
    "properties": {
      "byteRange": null,
      "daiPlacementOpportunity": null,
      "date": null,
      "discontinuity": false,
      "duration": 6,
      "title": "",
      "uri": "http://ad_seg2.ts"
    }
  },
  {
    "attributes": {
      "attributes": {}
    },
    "properties": {
      "byteRange": null,
      "daiPlacementOpportunity": null,
      "date": null,
      "discontinuity": false,
      "duration": 6,
      "title": "",
      "uri": "http://other_seg1.ts"
    }
  }
]

Here the CUE-data is not found in the newest segment, v0.4.0 parses and attaches it to the seg-Item above it, so when parsing the last seg-Item in mseq_2, there is still no CUE-data to be found. This bottom seg-Item would then be appended to the Segment List with no CUE-data. With none of the seg-Items in the List containing any CUE-data, would fail in truly representing the contents of the original media sequence.
It would be like the CUE-IN tag got removed when parsing.
(This issue stems from an older issue [email protected] package. An issue that has since been addressed in v.0.4.1)

Fix: Too Few Media Sequences Generated When Using TYPE-B

When enabling this.sequenceAlwaysContainNewSegments an after-loaded VOD would start on the next sequence. Eg if the previous vod ended on mseq 12, then the first mseq on the next vod would be mseq 13. Which would be expected but when used in CE would be considered too early.

Add fetch retry logic when loading HLS manifest

To avoid a long time out time when a manifest fetch attempt gets compromised for whatever unexpected reason,
I suggest extending the fetch function with the ability to set retries and max timeout.

Audio Media Sequences Adding More Segments Than Video (TYPE - B)

Currently, for cases where the vods used are demuxed and with different segment lengths, when generating the media sequences it may on occasion generate audio sequences that add more segments than it needs to - since the main sequence population logic heeds to the target Sequence Duration Rule. e.i. When creating an audio media sequence it may add 2 new segments because it has room for 2 more segments, while on the video side, it only has room for 1 new segment.

This means that the audio position is ahead of the video position, which is not an expected behavior and could result in CUE tags showing up at different points in time.

Future Work

Ideally, when creating the audio sequences we should add segments based on how many seconds were added in the video corresponding video sequence, rather than how many can fit before we go over the Sequence Duration.
But in order to achieve that, we would need to make more changes, eg. Generate all video sequences and deltaTimes first. Then the audio sequences and deltaTimes. Right now, in the code it generates the v/a sequences first then the v/a deltaTimes.

Delta times and positions are not correctly calculated on option 'sequenceAlwaysContainNewSegments'

The delta times and positions are not calculated correctly when the option sequenceAlwaysContainNewSegments is set.

When a sequence adds multiple new segments, eg. a 6s segment is ejected and two 3s segments are added, then the delta time should be based on the difference between the previous amount of new content (6s) and the new amount (3s x 2), i.e. delta time is 0. The same goes for the position calculation. 2 x 3s segments were added so the position should increase by 6.

Reload function splices from wrong start index

When calling reload() on a HLSVod containing common segments, e.g. loading same VOD back to back.
The reloaded HLSVod will be missing a few key segments. It Splices wrong.

The reload(...) function's purpose is to splice in foreign segments in the hlsvod at an accurate position.
It does this by splicing the list of segments at a start index that corresponds to the first seg-Item in the current media-Sequence. Since we want to splice new segments behind the current mseq.

Usually the mediaSeqNo for the current media-sequence would correspond to the correct starting index. However, in some cases, that is not true. The correct start index might actually be placed later than the index matching mediaSeqNo. So to address this, we perform a search in the list looking for a seg-Item that has a uri matching the URI for the first seg-Item in the current media-Sequence.

          let size = this.mediaSequences[mediaSeqNo].segments[allBandwidths[0]].length;
          let targetUri =  this.mediaSequences[mediaSeqNo].segments[allBandwidths[0]][0].uri;
          let targetPos = 0;
          for (let i = mediaSeqNo; i < this.segments[allBandwidths[0]].length; i++) {
            if (this.segments[allBandwidths[0]][i].uri === targetUri) {
              targetPos = i;
            }
          }
          allBandwidths.forEach(bw => this.segments[bw] = this.segments[bw].slice((targetPos), (targetPos + size)));

However, this solution make the assumption that the segment list (this.segments[<bw-key>]) will not contain multiple copies of the same seg-Item.

Byterange media playlists without offset not working

Media playlists that uses BYTERANGE and where offset is not specified on each segment does not work, example:

#EXTM3U
#EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version v2.5.1-9f11077-release
#EXT-X-TARGETDURATION:7
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="video-avc_1080p.mp4",BYTERANGE="870@0"
#EXTINF:6.000,
#EXT-X-BYTERANGE:1915768@1118
video-avc_1080p.mp4
#EXTINF:6.000,
#EXT-X-BYTERANGE:1783958
video-avc_1080p.mp4
#EXTINF:6.000,
#EXT-X-BYTERANGE:1175162
video-avc_1080p.mp4
#EXTINF:6.000,
#EXT-X-BYTERANGE:2092859
video-avc_1080p.mp4
#EXTINF:6.000,
#EXT-X-BYTERANGE:1863487
video-avc_1080p.mp4

According to the specification:

If o is not present, a previous Media Segment MUST appear in the
Playlist file and MUST be a sub-range of the same media resource, or
the Media Segment is undefined and the Playlist MUST be rejected.

which may not always be correct in all slices

Support loading VOD with subtitle tracks

Support for loading subtitle tracks can be implemented similarly to how support for audio tracks is implemented.

Although the logic for loading and mapping a vod after the "previous vod" might have to be implemented differently if we have another fallback track behavior in mind for subtitles.

Refactor and typescript support

This library has grown in functionality and code and it is definitely time to break this one js-file into more manageable sets of files. And at the same time convert this project to Typescript.

Support for EXT-X-BYTERANGE in HLS CMAF

Support for VOD that has EXT-X-BYTERANGE, e.g.

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-MAP:URI="media/VIDEO_e4da5fcd-5ffc-4713-bcdd-95ea579d790b_dv_p5_1080p-video-dvh1.mp4",BYTERANGE="857@0"
#EXTINF:6.000000,
#EXT-X-BYTERANGE:102033@1417
media/VIDEO_e4da5fcd-5ffc-4713-bcdd-95ea579d790b_dv_p5_1080p-video-dvh1.mp4
#EXTINF:6.000000,
#EXT-X-BYTERANGE:8092295@103450
media/VIDEO_e4da5fcd-5ffc-4713-bcdd-95ea579d790b_dv_p5_1080p-video-dvh1.mp4
#EXTINF:6.000000,
#EXT-X-BYTERANGE:5090140@8195745
media/VIDEO_e4da5fcd-5ffc-4713-bcdd-95ea579d790b_dv_p5_1080p-video-dvh1.mp4
...

Bug: When loading demux Vod after muxed Vod

With the setting sequenceAlwaysContainNewSegments=true, the new logic for dividing vod segments into sequences (in the fn _createMediaSequences()) generates Errors in an edge-case.

It assumed that this.audioSegments would always contain a segItem
eg.

{
  duration: 6.006,
  timelinePosition: null,
  cue: null,
  uri: "http://mock.com/level0/seg_50.ts",
},

But in the case where one loads a vod w/o audiotracks after a vod w/ audiotracks, or vice versa,
then with the loadAfter-logic in place, it would append null or undefined instead of the expected audio segment.
This means that this.audioSegments could include a null item. Which was not expected and would result in TypeError.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.