npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@gdty/m3u8-codec

v1.0.3

Published

m3u8 parser & generator

Downloads

6

Readme

@gdty/m3u8-codec

A parser/serializer library for M3U8 (HLS) manifests ...and maybe more?

import M3u8Codec from 'm3u8-codec';

const object = M3u8Codec.parse(m3u8Data);
const newM3u8Data = M3u8Codec.stringify(object);

CODECS

The library currently defines 4 codecs in order of increasing complexity:

LineCodec

LineCodec is the super class for ALL other codecs and has the bulk of the parsing/stringifying functionality. YOU SHOULD NOT USE THIS CODEC DIRECTLY as it only operates on a line-level and it's more convenient to use M3U8Codec, described below, if you need extremely low-level access.

LineCodec#parse

Parses a single line at a time and returns a single object for the entire line that is either a tag, comment, uri, or empty.

Example:

const codec = new LineCodec();

codec.parse('#EXT-X-DATERANGE:ID="foo",START-DATE=2012-12-25T14:12:34.123Z,FOO="quoted-string, here"');

// Results in:
{
  name: '#EXT-X-DATERANGE',
  type: '<attribute-list>',
  playlistType: 'media',
  appliesToNextUri: true,
  value: [
    {
      name: 'ID',
      type: '<quoted-string>',
      value: 'foo'
    },
    {
      name: 'START-DATE',
      type: '<date-time-msec>',
      value: new Date('2012-12-25T14:12:34.123Z')
    },
    {
      name: 'FOO',
      type: '<quoted-string>',
      value: 'quoted-string, here'
    }
  ]
}

LineCodec#stringify

Serializes a single object that is either a tag, comment, uri, or empty and returns the string representation.

const string = codec.stringify(parsed);

// string =>
`#EXT-X-DATERANGE:ID="foo",START-DATE=2012-12-25T14:12:34.123Z,FOO="quoted-string, here"`

M3U8Codec extends LineCodec

M3U8Codec is extremely low-level and it provides an array of line objects that are in-order and can be re-serialized without any data loss. The output should only differ from the input in whitespace (the result of some trimming done for convenience).

M3U8Codec#parse

Parses an entire file and returns an array of line-objects.

const codec = new M3U8Codec();

codec.parse(`#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
\t
# A comment here
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXTINF:10,testing this, thing!""
#EXT-X-BYTERANGE:522828@0
#EXT-X-CUE-OUT:20
hls_450k_video.ts`);

// Results in:
[
  {
    name: '#EXTM3U',
    playlistType: 'both'
  },
  {
    name: '#EXT-X-TARGETDURATION',
    type: '<decimal-integer>',
    playlistType: 'media',
    value: 10
  },
  {
    name: '#EXT-X-VERSION',
    type: '<decimal-integer>',
    playlistType: 'both',
    value: 4
  },
  {
    name: '#EXT-X-ALLOW-CACHE',
    type: '<enumerated-string>',
    playlistType: 'media',
    maxVersion: 6,
    value: 'YES'
  },
  {
    name: '#EXT-X-MEDIA-SEQUENCE',
    type: '<decimal-integer>',
    playlistType: 'media',
    value: 0
  },
  {
    name: 'empty',
    type: '<empty-line>',
    value: '\t'
  },
  {
    name: 'comment',
    type: '<comment-line>',
    value: '# A comment here'
  },
  {
    name: '#EXT-X-PROGRAM-DATE-TIME',
    type: '<date-time-msec>',
    playlistType: 'media',
    appliesToNextUri: true,
    value: new Date('2012-12-25T14:12:34.123Z')
  },
  {
    name: '#EXTINF',
    type: '<decimal-floating-point-duration>',
    playlistType: 'media',
    appliesToNextUri: true,
    value: {
      'duration': 10,
      'title': 'testing this, thing!""'
    }
  },
  {
    name: '#EXT-X-BYTERANGE',
    type: '<decimal-byterange>',
    playlistType: 'media',
    appliesToNextUri: true,
    minVersion: 4,
    value: {
      length: 522828,
      offset: 0
    }
  },
  {
    name: '#EXT-X-CUE-OUT',
    type: '<decimal-floating-point>',
    playlistType: 'media',
    appliesToNextUri: true,
    isCustom: true,
    value: 20
  },
  {
    name: 'uri',
    type: '<uri-line>',
    value: 'hls_450k_video.ts'
  }
]

M3U8Codec#stringify

Takes an array and generates a single string representing the resulting M3U8 manifest.

const string = codec.stringify(parsed);

// string =>
`#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
\t
# A comment here
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXTINF:10,testing this, thing!""
#EXT-X-BYTERANGE:522828@0
#EXT-X-CUE-OUT:20
hls_450k_video.ts
#EXT-X-DISCONTINUITY-SEQUENCE:0`

NOTE: An #EXT-X-DISCONTINUITY-SEQUENCE tag was added because it has a default value if one isn't provided.

M3U8NestedCodec extends M3U8Codec

M3U8NestedCodec further processes the line-objects and produces a more meaningful representation. Tags are grouped into sets based on their purpose. All global tags are collected and stored, in order. All uri lines and any tags that relate to those uris are also collected into individual arrays - again preserving order. Comments are intelligently collected along with the tags they precede and end up in either the global or per-playlist/per-segment collections.

There is some contextual information lost in this transform. The resulting serialized object is functionally similar but, for instance, global tags are all at the top of the file.

M3U8NestedCodec#parse

Parses an entire file and returns an object with a globals property - an array of global tags uri and comments. And either a playlists property - an array containing arrays of tags that represent a single playlist. Or a segments property - an array containing arrays of tags all related to a single segment.

const codec = new M3U8NestedCodec();

codec.parse(`#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
\t
# A comment here
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXTINF:10,testing this, thing!""
#EXT-X-BYTERANGE:522828@0
#EXT-X-CUE-OUT:20
hls_450k_video.ts`);

// Results in:
{
  playlistType: 'media',
  globals: [
    {
      name: '#EXTM3U',
      playlistType: 'both'
    {
      name: '#EXT-X-TARGETDURATION',
      type: '<decimal-integer>',
      playlistType: 'media',
      value: 10
    },
    {
      name: '#EXT-X-VERSION',
      type: '<decimal-integer>',
      playlistType: 'both',
      value: 4
    },
    {
      name: '#EXT-X-ALLOW-CACHE',
      type: '<enumerated-string>',
      playlistType: 'media',
      maxVersion: 6,
      value: 'YES'
    },
    {
      name: '#EXT-X-MEDIA-SEQUENCE',
      type: '<decimal-integer>',
      playlistType: 'media',
      value: 0
    },
    {
      name: '#EXT-X-DISCONTINUITY-SEQUENCE',
      type: '<decimal-integer>',
      value: 0
    }
  ],
  segments: [
    [
      {
        name: 'comment',
        type: '<comment-line>',
        value: '# A comment here'
      },
      {
        name: '#EXT-X-PROGRAM-DATE-TIME',
        type: '<date-time-msec>',
        playlistType: 'media',
        appliesToNextUri: true,
        value: new Date('2012-12-25T14:12:34.123Z')
      },
      {
        name: '#EXTINF',
        type: '<decimal-floating-point-duration>',
        playlistType: 'media',
        appliesToNextUri: true,
        value: {
          duration: 10,
          title: 'testing this, thing!""'
        }
      },
      {
        name: '#EXT-X-BYTERANGE',
        type: '<decimal-byterange>',
        playlistType: 'media',
        appliesToNextUri: true,
        minVersion: 4,
        value: {
          length: 522828,
          offset: 0
        }
      },
      {
        name: '#EXT-X-CUE-OUT',
        type: '<decimal-floating-point>',
        playlistType: 'media',
        appliesToNextUri: true,
        isCustom: true,
        value: 20
      },
      {
        name: 'uri',
        type: '<uri-line>',
        value: 'hls_450k_video.ts'
      }
    ]
  ]
}

M3U8NestedCodec#stringify

const string = codec.stringify(parsed);

// string =>
`#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
# A comment here
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXTINF:10,testing this, thing!""
#EXT-X-BYTERANGE:522828@0
#EXT-X-CUE-OUT:20
hls_450k_video.ts`

NOTE: Empty lines have been removed from the output.

VideojsCodec extends M3U8NestedCodec

VideojsCodec is a m3u8-parser compatible output format. The format is extremely terse but results in a huge loss of context. The reconstructed output is functionally identical to the input but will have lost all comments and sequence. Certain optimizations (like byte-range shorthand) will be missing if they were present in the input.

VideojsCodec#parse

const codec = new VideojsCodec();

const parsed = codec.parse(`#EXTM3U
#EXT-X-TARGETDURATION:10
#A global comment here...
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0

# A segment comment here...
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXTINF:10,testing this, thing!""
#EXT-X-BYTERANGE:522828@0
#EXT-X-CUE-OUT:20
hls_450k_video.ts`);

// parsed =>
{
    allowCache: true,
    discontinuityStarts: [],
    segments: [
    {
        timeline: 0,
        duration: 10,
        dateTimeString: '2012-12-25T14:12:34.123Z',
        dateTimeObject: new Date('2012-12-25T14:12:34.123Z'),
        byterange:
        {
            length: 522828,
            offset: 0
        },
        comments: [
          '# A segment comment here...'
        ],
        cueOut: 20,
        uri: 'hls_450k_video.ts'
    }],
    comments: [
      '#A global comment here...'
    ],
    targetDuration: 10,
    version: 4,
    mediaSequence: 0,
    discontinuitySequence: 0,
    dateTimeString: '2012-12-25T14:12:34.123Z',
    dateTimeObject: new Date('2012-12-25T14:12:34.123Z')
}

VideojsCodec#stringify

const string = codec.stringify(parsed);

// string =>
`#EXTM3U
#A global comment here...
#EXT-X-VERSION:4
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
# A segment comment here...
#EXTINF:10,
#EXT-X-BYTERANGE:522828@0
#EXT-X-PROGRAM-DATE-TIME:2012-12-25T14:12:34.123Z
#EXT-X-CUE-OUT:20
hls_450k_video.ts`

NOTE: Empty lines have been removed from output. The order of tags and comments is not preserved.

TODO

  1. Validation: The bones are all there for a more strict validator. Each tag definition has minVersion maxVersion, allowed and/or required as applicable. Just need to run a validation pass once the parsing is complete.

License

Apache-2.0. Copyright (c) Ossum Technology Inc. (dba Adventr)