{"id":30883282,"url":"https://github.com/globocom/go-m3u8","last_synced_at":"2026-03-06T01:35:41.691Z","repository":{"id":282409529,"uuid":"947911222","full_name":"globocom/go-m3u8","owner":"globocom","description":"A m3u8 parser in Go for HTTP Live Streaming (HLS).","archived":false,"fork":false,"pushed_at":"2025-08-22T18:31:27.000Z","size":350,"stargazers_count":21,"open_issues_count":0,"forks_count":0,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-08-22T21:04:45.667Z","etag":null,"topics":["golang","hls","livestream","m3u8","video"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/globocom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-13T12:50:37.000Z","updated_at":"2025-08-22T18:31:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"2da2dd76-a0b9-4d48-9ad5-72a555ba7985","html_url":"https://github.com/globocom/go-m3u8","commit_stats":null,"previous_names":["globocom/go-m3u8"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/globocom/go-m3u8","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globocom%2Fgo-m3u8","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globocom%2Fgo-m3u8/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globocom%2Fgo-m3u8/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globocom%2Fgo-m3u8/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/globocom","download_url":"https://codeload.github.com/globocom/go-m3u8/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globocom%2Fgo-m3u8/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274166943,"owners_count":25233959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-08T02:00:09.813Z","response_time":121,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","hls","livestream","m3u8","video"],"created_at":"2025-09-08T09:44:11.271Z","updated_at":"2026-03-06T01:35:41.671Z","avatar_url":"https://github.com/globocom.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=center\u003e\u003cimg src=\"gopher.png\" height=100px\u003e\u003c/div\u003e\n\u003ch1 align=center\u003e\ngo-m3u8\n\u003cdiv align=center\u003e\n\u003cimg src=\"https://github.com/globocom/go-m3u8/actions/workflows/go.yml/badge.svg\"\u003e\n\u003ca href=\"https://goreportcard.com/report/github.com/globocom/go-m3u8\"\u003e\u003cimg src=\"https://goreportcard.com/badge/github.com/globocom/go-m3u8\"/\u003e\u003c/a\u003e\n\u003cimg src=\"https://img.shields.io/github/go-mod/go-version/globocom/go-m3u8\"\u003e\n\u003c/div\u003e\n\u003c/h1\u003e\n\n\n### ⚠️ Work in Progress!\n\n_This project is currently in pre-release and is subject to changes._\n\n## About\n\nA m3u8 parser library in Go to facilitate the manipulation of HTTP Live Streaming (HLS) manifests.\n\nThe library provides:\n\n- **Doubly Linked List Architecture -** Efficient playlist representation with ordered element access and manipulation.\n- **RFC 8216 Compliance -** Support for HLS tags based on the official [RFC](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis) documentation.\n- **Live Streaming Support -** Optimized for live streaming manifests only (no VoD support yet).\n\n## Architecture\n\n### Doubly Linked List\n\nThe m3u8 manifest is parsed into a **doubly linked list**. Each node on the list represents an element of the original HLS playlist (e.g. a tag, a comment, etc).\n\nThis data structure allows us to access the manifest in a sorted manner, to retrieve information and apply operations to its content.\n\nSome available operations are:\n\n- **Create** a new node, to represent an element of the playlist.\n- **Insert** a new node into the playlist, either at the end or before/after an existing node.\n- **Find** a specific node or a list of nodes, based on the element name.\n\n### HLS Elements\n\nTo guarantee scalibility, our library considers a data structure for the HLS elements that follows the [RFC documentation](https://tools.ietf.org/html/rfc8216). \n\nThe [**tags**](/tags) package implements the currently supported [Playlist Tags](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4):\n\n1. **basic -** Basic Tags (Section 4.4.1).\n- `#EXTM3U`\n- `#EXT-X-VERSION`\n\n2. **exclusive -** Media or Multivariant Playlist Tags (Section 4.4.2).\n- `#EXT-X-INDEPENDENT-SEGMENTS`\n- `#EXT-X-DEFINE`\n\n3. **media -** Media Playlist, Metadata and Segment Tags (Sections 4.4.3 to 4.4.5).\n- `#EXT-X-DATERANGE`\n- `#EXT-X-TARGETDURATION`\n- `#EXT-X-MEDIA-SEQUENCE`\n- `#EXT-X-DISCONTINUITY-SEQUENCE`\n- `#EXT-X-I-FRAMES-ONLY`\n- `#EXTINF`\n- `#EXT-X-DISCONTINUITY`\n- `#EXT-X-PROGRAM-DATE-TIME`\n- `#EXT-X-KEY`\n- `#EXT-X-MAP`\n- `#EXT-X-ENDLIST`\n\n4. **multivariant -** Multivariant Playlist Tags (Section 4.4.6).\n- `#EXT-X-STREAM-INF`\n- `#EXT-X-MEDIA`\n- `#EXT-X-I-FRAME-STREAM-INF`\n- `#EXT-X-SESSION-KEY`\n\n5. **others -** The tags in this section are \"non-official\" and are not listed in the RFC, e.g. tags added to the manifest by the live stream packaging service.\n- `#EXT-X-CUE-OUT`\n- `#EXT-X-CUE-IN`\n- Packager specific tags.\n- In-manifest comments (begin with `#` and are NOT tags).\n\n## Getting Started\n\n### Installation\n\nTo import the library to your Go project, run:\n\n```sh\ngo install github.com/globocom/go-m3u8\n```\n\nThe following [**Makefile**](/Makefile) commands are available:\n\n```Makefile\nmake test \t\t# Run test suites\nmake lint \t\t# Run code linter\n```\n\nThe [**testlocal**](/testlocal/) folder contains instructions on how to setup and test the library for local experimentation.\n\n### Decoding a Playlist\n\nThe `ParsePlaylist` method receives a `io.ReadCloser` object as argument and returns a `Playlist` object.\n\nYou may decode a manifest that is in string format:\n```go\nmanifest := `#EXTM3U\n#EXT-X-VERSION:3\n# variants\n#EXT-X-STREAM-INF:BANDWIDTH=479000,AVERAGE-BANDWIDTH=435000,CODECS=\"mp4a.40.2,avc1.64001F\",RESOLUTION=512x288,FRAME-RATE=30\nchannel_01.m3u8`\n\nmanifestReader := io.NopCloser(strings.NewReader(manifest))\n\nplaylist, err := m3u8.ParsePlaylist(manifestReader)\nif err != nil {\n\tpanic(err)\n}\n```\n\nOr read the manifest file directly:\n```go\nfile, _ := os.Open(\"multivariant.m3u8\")\n\nplaylist, err := go_m3u8.ParsePlaylist(file)\nif err != nil {\n\tpanic(err)\n}\n```\n\n### Encoding a Playlist\n\nThe `EncodePlaylist` method parses a `Playlist` object back into string format.\n\n```go\nmanifest, err := go_m3u8.EncodePlaylist(playlist)\nif err != nil {\n\tpanic(err)\n}\n```\n\n## Usage \n\nFor complete details on the available methods, please read [the original release notes](https://github.com/globocom/go-m3u8/releases/tag/v0.1.0).\n\n### Collecting Ad Break Data\n\nCollect information on ad breaks present on the manifest when SCTE-35 ad insertion is used.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\tgo_m3u8 \"github.com/globocom/go-m3u8\"\n\tm3u8_pl \"github.com/globocom/go-m3u8/playlist\"\n\tm3u8_tags \"github.com/globocom/go-m3u8/tags\"\n)\n\ntype AdBreak struct {\n\tTimestamp     string\n\tMediaSequence int\n\tStatus        string\n}\n\nfunc main() {\n\tfile, _ := os.Open(\"playlist.m3u8\")\n\tp, err := go_m3u8.ParsePlaylist(file)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlatestBreak := GetLatestBreakData(p)\n\n\tfmt.Printf(\"%+v\\n\", latestBreak)\n}\n\nfunc GetLatestBreakData(manifest *m3u8_pl.Playlist) AdBreak {\n\tadBreaks := manifest.Breaks()\n\n\tif len(adBreaks) == 0 {\n\t\treturn AdBreak{}\n\t}\n\n\tlatestAdBreak := adBreaks[len(adBreaks)-1]\n\n\tbreakStatus := latestAdBreak.HLSElement.Details[\"Status\"]\n\tif (breakStatus == m3u8_tags.BreakStatusLeavingDVR) || (breakStatus == m3u8_tags.BreakStatusNotReady) {\n\t\treturn AdBreak{}\n\t}\n\n\tif latestAdBreak.HLSElement.Attrs[\"PLANNED-DURATION\"] == \"\" {\n\t\treturn AdBreak{Status: \"invalid\"}\n\t}\n\n\tstartMediaSequence, err := strconv.Atoi(latestAdBreak.HLSElement.Details[\"StartMediaSequence\"])\n\tif err != nil {\n\t\treturn AdBreak{Status: \"invalid\"}\n\t}\n\n\tstartDate, err := time.Parse(time.RFC3339Nano, latestAdBreak.HLSElement.Attrs[\"START-DATE\"])\n\tif err != nil {\n\t\treturn AdBreak{Status: \"invalid\"}\n\t}\n\n\tadBreak := AdBreak{\n\t\tMediaSequence: startMediaSequence,\n\t\tTimestamp:     fmt.Sprintf(\"%d\", startDate.Unix()),\n\t\tStatus:        \"valid\",\n\t}\n\n\treturn adBreak\n}\n```\n\n### Adding Ad Break Markers\n\nInsert SCTE-35 ad break markers at specific segments.\n\n```go\npackage main\n\nimport (\n\t\"os\"\n\n\tgo_m3u8 \"github.com/globocom/go-m3u8\"\n\tm3u8_tags \"github.com/globocom/go-m3u8/tags\"\n)\n\nfunc main() {\n\tfile, _ := os.Open(\"playlist.m3u8\")\n\tp, err := go_m3u8.ParsePlaylist(file)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Find all segments from media playlist\n\t// Suppose there are 12 segments, each with a duration of 3.2s\n\tsegments := p.Segments()\n\n\t// Find a specific segment to insert ad break before\n\tsegmentNodeBreakStart := segments[2]\n\n\t// Create date range tag with SCTE35\n\tattrs := map[string]string{\n\t\t\"ID\":               \"1-1747402436\",\n\t\t\"START-DATE\":       \"2025-05-16T13:33:56.266666Z\",\n\t\t\"PLANNED-DURATION\": \"16.0\",\n\t\t\"SCTE-OUT\":         \"0x123abc456def\",\n\t}\n\tdateRangeNode := p.NewNode(m3u8_tags.DateRangeName, \"\", attrs, nil)\n\n\t// Create event cue out tag\n\tcueOutNode := p.NewNode(m3u8_tags.EventCueOutName, \"\", map[string]string{m3u8_tags.EventCueOutTag: \"16.0\"}, nil)\n\n\t// Insert ad break start\n\tp.InsertBefore(segmentNodeBreakStart, dateRangeNode)\n\tp.InsertAfter(dateRangeNode, cueOutNode)\n\n\t// Find the last segment inside ad break\n\tsegmentNodeBreakEnd := segments[8]\n\n\t// Create event cue in tag\n\tcueInNode := p.NewNode(m3u8_tags.EventCueInName, \"\", map[string]string{m3u8_tags.EventCueInTag: \"\"}, nil)\n\n\t// Insert ad break end\n\tp.InsertAfter(segmentNodeBreakEnd, cueInNode)\n\n  // Encode the playlist back into manifest format\n\tmanifest, err := go_m3u8.EncodePlaylist(p)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprint(manifest)\n}\n```\n\n### Adding Discontinuity Information\n\nInsert discontinuity tags when SCTE-35 ad break markers are present.\n\n```go\npackage main\n\nimport (\n\t\"os\"\n\n\tgo_m3u8 \"github.com/globocom/go-m3u8\"\n\tm3u8_tags \"github.com/globocom/go-m3u8/tags\"\n)\n\nfunc main() {\n\tfile, _ := os.Open(\"playlist.m3u8\")\n\tp, err := go_m3u8.ParsePlaylist(file)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Find all ad break markers (date range tags with scte-out \u0026 cue-in events)\n\tdateRangeNodes := p.Breaks()\n\tcueInNodes := p.CueInEvents()\n\n\t// Insert discontinuity before each ad break start\n\tfor _, dateRangeNode := range dateRangeNodes {\n\t\tdiscontinuityNode := p.NewNode(m3u8_tags.DiscontinuityName, \"\", nil, nil)\n\t\tp.InsertBefore(dateRangeNode, discontinuityNode)\n\t}\n\n\t// Insert discontinuity after each ad break end\n\tfor _, cueInNode := range cueInNodes {\n\t\tdiscontinuityNode := p.NewNode(m3u8_tags.DiscontinuityName, \"\", nil, nil)\n\t\tp.InsertBefore(cueInNode, discontinuityNode)\n\t}\n\n\t// Encode the playlist back into manifest format\n\tmanifest, err := go_m3u8.EncodePlaylist(p)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprint(manifest)\n}\n```\n\n### Updating Encryption Keys\n\nRotate encryption keys for enhanced security.\n\n```go\npackage main\n\nimport (\n\t\"os\"\n\tgo_m3u8 \"github.com/globocom/go-m3u8\"\n)\n\nfunc main() {\n\tfile, _ := os.Open(\"playlist.m3u8\")\n\tp, err := go_m3u8.ParsePlaylist(file)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Find all encryption key tags\n\tkeyNodes := p.EncryptionTags()\n\t\n\tfor _, keyNode := range keyNodes {\n\t\t// Update the key URI with new key server\n\t\tif keyNode.HLSElement.Attrs != nil {\n\t\t\tkeyNode.HLSElement.Attrs[\"URI\"] = \"https://new-key-server.com/key.bin\"\n\t\t\tkeyNode.HLSElement.Attrs[\"IV\"] = \"0x12345678901234567890123456789012\"\n\t\t}\n\t}\n\n\tp.Print()\n}\n```\n\n## Extra Docs\n\n- [Handling Ad Breaks](docs/HandlingAdBreaks.md)\n- [Handling Invalid Ad Breaks](docs/HandlingInvalidAdBreaks.md)\n\n## Contributing\n\nAs this is an open-source project, we encourage and support any community contributions!\n\nFeel free to report bugs, request features and recommend improvements. Multiple repository mantainers are keeping an eye on opened issues and pull requests.\n\n## License\n\nReleased under the [MIT License](/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobocom%2Fgo-m3u8","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglobocom%2Fgo-m3u8","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobocom%2Fgo-m3u8/lists"}