├── .gitignore ├── .npmignore ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── lib └── parser.min.js ├── package.json ├── src ├── asx.coffee ├── m3u.coffee └── pls.coffee └── test ├── bad_extended.m3u ├── bad_extended2.m3u ├── bad_extended3.m3u ├── example.asx ├── example.pls ├── extended.m3u ├── malformed.asx ├── malformed_no_attributes.asx ├── malformed_wrong_case.asx ├── mixed.m3u8 ├── negative_time.m3u ├── pound_hash_name.m3u ├── test.coffee └── url.m3u /.gitignore: -------------------------------------------------------------------------------- 1 | lib/parser.js 2 | node_modules/ 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | 4 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | license = require('fs').readFileSync './LICENSE', encoding: 'utf8' 2 | 3 | module.exports = (grunt) -> 4 | grunt.initConfig 5 | coffee: 6 | compile: 7 | files: 8 | 'lib/parser.js': ['src/*.coffee'] 9 | mochaTest: 10 | test: 11 | options: 12 | reporter: 'nyan' 13 | src: ['test/test.coffee'] 14 | uglify: 15 | options: 16 | banner: "/*\n#{license}\n*/\n" 17 | target: 18 | files: 19 | 'lib/parser.min.js': ['lib/parser.js'] 20 | 21 | grunt.loadNpmTasks 'grunt-contrib-coffee' 22 | grunt.loadNpmTasks 'grunt-mocha-test' 23 | grunt.loadNpmTasks 'grunt-contrib-uglify' 24 | 25 | grunt.registerTask 'default', ['coffee', 'uglify','mochaTest'] 26 | grunt.registerTask 'test', ['coffee', 'uglify', 'mochaTest'] 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is dual licensed under the MIT and Beerware license. 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2013 Nick Desaulniers 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | "THE BEER-WARE LICENSE" (Revision 42): 25 | wrote this file. As long as you retain this 26 | notice you can do whatever you want with this stuff. If we meet some day, 27 | and you think this stuff is worth it, you can buy me a beer in return. 28 | Nick Desaulniers 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # javascript-playlist-parser # 2 | Parse m3u, m3u extended, pls, and asx in JavaScript. 3 | 4 | ## Usage ## 5 | ### Browser ### 6 | Adds `window.M3U.parse`, `window.PLS.parse`, and `window.ASX.parse` which take a 7 | string and return a possibly empty array of objects. 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | ```javascript 14 | // Fetch the playlist file, using xhr for example 15 | var xhr = new XMLHttpRequest(); 16 | xhr.open("GET", "my_playlist.m3u"); 17 | xhr.overrideMimeType("audio/x-mpegurl"); // Needed, see below. 18 | xhr.onload = parse; 19 | xhr.send(); 20 | 21 | // Parse it 22 | function parse () { 23 | var playlist = M3U.parse(this.response); 24 | var audio = new Audio(); 25 | next(audio, playlist, 0); 26 | }; 27 | 28 | // Play each song after a song finishes 29 | function next (audio, playlist, i) { 30 | if (i < playlist.length) { 31 | audio.src = playlist[i++].file; 32 | audio.onended = next.bind(null, audio, playlist, i); 33 | audio.play(); 34 | } 35 | }; 36 | ``` 37 | 38 | [Demo](http://nickdesaulniers.github.io/javascript-playlist-parser/) 39 | 40 | ### Node.js ### 41 | Adds `require('playlist-parser').M3U.parse`, 42 | `require('playlist-parser').PLS.parse`, 43 | and `require('playlist-parser').ASX.parse` 44 | which take a string and return 45 | a possibly empty array of objects. 46 | 47 | `npm install playlist-parser` 48 | ```javascript 49 | var parsers = require("playlist-parser"); 50 | var M3U = parsers.M3U; 51 | 52 | var fs = require("fs"); 53 | var playlist = M3U.parse(fs.readFileSync("my_playlist.m3u", { encoding: "utf8" })); 54 | ``` 55 | ## Return Values ## 56 | Calls to parse return an array of objects that look like: 57 | 58 | ### M3U Simple or ASX ### 59 | ```javascript 60 | [{ 61 | file: "http://song.com/song.mp3" 62 | }] 63 | ``` 64 | 65 | ### M3U Extended ### 66 | ```javascript 67 | [{ 68 | length: 1234, 69 | artist: "Iron Maiden", 70 | title: "Rime of the Ancient Mariner", 71 | file: "http://song.com/song.mp3" 72 | }] 73 | ``` 74 | 75 | ### PLS ### 76 | ```javascript 77 | [{ 78 | file: "http://song.com/song.mp3", 79 | title: "My favorite song ever by my favorite artist", 80 | length: 1234 81 | }] 82 | ``` 83 | 84 | ## MIME Types ## 85 | * m3u -> audio/x-mpegurl 86 | * pls -> audio/x-scpls 87 | * asx -> video/x-ms-asf 88 | 89 | ## License ## 90 | 91 | This software is dual licensed under the MIT and Beerware license. 92 | 93 | The MIT License (MIT) 94 | 95 | Copyright (c) 2013 Nick Desaulniers 96 | 97 | Permission is hereby granted, free of charge, to any person obtaining a copy of 98 | this software and associated documentation files (the "Software"), to deal in 99 | the Software without restriction, including without limitation the rights to 100 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 101 | the Software, and to permit persons to whom the Software is furnished to do so, 102 | subject to the following conditions: 103 | 104 | The above copyright notice and this permission notice shall be included in all 105 | copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 108 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 109 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 110 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 111 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 112 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 113 | 114 | "THE BEER-WARE LICENSE" (Revision 42): 115 | wrote this file. As long as you retain this 116 | notice you can do whatever you want with this stuff. If we meet some day, 117 | and you think this stuff is worth it, you can buy me a beer in return. 118 | Nick Desaulniers 119 | 120 | -------------------------------------------------------------------------------- /lib/parser.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | This software is dual licensed under the MIT and Beerware license. 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2013 Nick Desaulniers 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | "THE BEER-WARE LICENSE" (Revision 42): 26 | wrote this file. As long as you retain this 27 | notice you can do whatever you want with this stuff. If we meet some day, 28 | and you think this stuff is worth it, you can buy me a beer in return. 29 | Nick Desaulniers 30 | 31 | */ 32 | (function(){var a,b,c;a=("undefined"!=typeof window&&null!==window?window.DOMParser:void 0)||("function"==typeof require?require("xmldom").DOMParser:void 0)||function(){},b=function(a,c){var d,e,f,g,h,i,j,k,l,m,n;if(a.hasChildNodes())for(g=a.childNodes,h=k=0,m=g.length;m>=0?m>k:k>m;h=m>=0?++k:--k)if(e=g[h],f=e.nodeName,/REF/i.test(f)){for(d=e.attributes,j=l=0,n=d.length;n>=0?n>l:l>n;j=n>=0?++l:--l)if(i=d[j].nodeName.match(/HREF/i)){c.push({file:e.getAttribute(i[0]).trim()});break}}else"#text"!==f&&b(e,c);return null},c=function(c){var d,e;return e=[],(d=(new a).parseFromString(c,"text/xml").documentElement)?(b(d,e),e):e},("undefined"!=typeof module&&null!==module?module.exports:window).ASX={name:"asx",parse:c}}).call(this),function(){var a,b,c,d,e,f,g;b="#EXTM3U",a=/:(?:(-?\d+),(.+)\s*-\s*(.+)|(.+))\n(.+)/,e=function(b){var c;return c=b.match(a),c&&6===c.length?{length:c[1]||0,artist:c[2]||"",title:c[4]||c[3],file:c[5].trim()}:void 0},g=function(a){return{file:a.trim()}},d=function(a){return!!a.trim().length},c=function(a){return"#"!==a[0]},f=function(a){var f;return a=a.replace(/\r/g,""),f=a.search("\n"),a.substr(0,f)===b?a.substr(f).split("\n#").filter(d).map(e):a.split("\n").filter(d).filter(c).map(g)},("undefined"!=typeof module&&null!==module?module.exports:window).M3U={name:"m3u",parse:f}}.call(this),function(){var a,b;a=/(file|title|length)(\d+)=(.+)\r?/i,b=function(b){var c,d,e,f,g,h,i,j,k,l;for(g=[],l=b.trim().split("\n"),j=0,k=l.length;k>j;j++)e=l[j],f=e.match(a),f&&4===f.length&&(i=f[0],d=f[1],c=f[2],h=f[3],g[c]||(g[c]={}),g[c][d.toLowerCase()]=h);return g.filter(function(a){return null!=a})},("undefined"!=typeof module&&null!==module?module.exports:window).PLS={name:"pls",parse:b}}.call(this); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playlist-parser", 3 | "version": "0.0.12", 4 | "description": "Parse m3u, m3u extended, and pls", 5 | "main": "lib/parser.min.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "grunt test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/nickdesaulniers/javascript-playlist-parser.git" 15 | }, 16 | "keywords": [ 17 | "javascript", 18 | "playlist", 19 | "parse", 20 | "m3u", 21 | "pls", 22 | "asx" 23 | ], 24 | "author": "Nick Desaulniers", 25 | "license": "Dual MIT & Beerware", 26 | "bugs": { 27 | "url": "https://github.com/nickdesaulniers/javascript-playlist-parser/issues" 28 | }, 29 | "devDependencies": { 30 | "chai": "1.7.2", 31 | "grunt-contrib-coffee": "0.7.0", 32 | "grunt-mocha-test": "0.6.3", 33 | "grunt-contrib-uglify": "~0.2.4" 34 | }, 35 | "dependencies": { 36 | "xmldom": "0.1.21" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/asx.coffee: -------------------------------------------------------------------------------- 1 | DOMParser = window?.DOMParser or require?('xmldom').DOMParser or -> 2 | 3 | # pre order, depth first 4 | find = (node, list) -> 5 | if node.hasChildNodes() 6 | childNodes = node.childNodes 7 | for i in [0...childNodes.length] 8 | childNode = childNodes[i] 9 | childNodeName = childNode.nodeName 10 | if /REF/i.test childNodeName 11 | attributes = childNode.attributes 12 | for x in [0...attributes.length] 13 | match = attributes[x].nodeName.match /HREF/i 14 | if match 15 | list.push file: childNode.getAttribute(match[0]).trim() 16 | break 17 | else if childNodeName isnt '#text' 18 | find childNode, list 19 | null 20 | 21 | parse = (playlist) -> 22 | ret = [] 23 | doc = (new DOMParser()).parseFromString(playlist, 'text/xml').documentElement 24 | return ret unless doc 25 | find doc, ret 26 | ret 27 | 28 | (if module? then module.exports else window).ASX = 29 | name: 'asx' 30 | parse: parse 31 | 32 | -------------------------------------------------------------------------------- /src/m3u.coffee: -------------------------------------------------------------------------------- 1 | # http://gonze.com/playlists/playlist-format-survey.html#M3U 2 | 3 | EXTENDED = '#EXTM3U' 4 | COMMENT_RE = /:(?:(-?\d+),(.+)\s*-\s*(.+)|(.+))\n(.+)/ 5 | 6 | # #EXTINF:822,Iron Maiden - Rime of the Ancient Mariner 7 | extended = (line) -> 8 | match = line.match COMMENT_RE 9 | if match and match.length is 6 10 | length: match[1] or 0 11 | artist: match[2] or '' 12 | title: match[4] or match[3] 13 | file: match[5].trim() 14 | 15 | simple = (string) -> 16 | file: string.trim() 17 | 18 | empty = (line) -> 19 | !!line.trim().length 20 | 21 | comments = (line) -> 22 | line[0] isnt '#' 23 | 24 | parse = (playlist) -> 25 | playlist = playlist.replace /\r/g, '' 26 | firstNewline = playlist.search '\n' 27 | if playlist.substr(0, firstNewline) is EXTENDED 28 | playlist.substr(firstNewline).split('\n#').filter(empty).map extended 29 | else 30 | playlist.split('\n').filter(empty).filter(comments).map simple 31 | 32 | (if module? then module.exports else window).M3U = 33 | name: 'm3u' 34 | parse: parse 35 | 36 | -------------------------------------------------------------------------------- /src/pls.coffee: -------------------------------------------------------------------------------- 1 | LISTING_RE = /(file|title|length)(\d+)=(.+)\r?/i 2 | 3 | parse = (playlist) -> 4 | tracks = [] 5 | for line in playlist.trim().split '\n' 6 | match = line.match LISTING_RE 7 | if match and match.length is 4 8 | [_, key, index, value] = match 9 | tracks[index] = {} unless tracks[index] 10 | tracks[index][key.toLowerCase()] = value 11 | tracks.filter (track) -> track? 12 | 13 | (if module? then module.exports else window).PLS = 14 | name: 'pls' 15 | parse: parse 16 | 17 | -------------------------------------------------------------------------------- /test/bad_extended.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1,http://radio.4duk.ru:80/4duk40.mp3 3 | http://radio.4duk.ru:80/4duk40.mp3 4 | -------------------------------------------------------------------------------- /test/bad_extended2.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1,Flumotion Stream 3 | http://195.10.10.206/copesedes/pamplona-low.mp3?GKID=a6a781cca19511e49ff300163e25ebaa -------------------------------------------------------------------------------- /test/bad_extended3.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:france_inter_mp3 3 | http://mp3.live.tv-radio.com/franceinter/all/franceinterhautdebit.mp3 4 | -------------------------------------------------------------------------------- /test/example.asx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 1 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/example.pls: -------------------------------------------------------------------------------- 1 | [playlist] 2 | numberofentries=3 3 | File1=http://94.23.216.58:8800 4 | Title1=(#1 - 35/2000) NOISEfm.pl - Niezalezne Radio Rock ! 5 | Length1=-1 6 | File2=http://94.23.216.58:8000 7 | Title2=(#2 - 826/3000) NOISEfm.pl - Niezalezne Radio Rock ! 8 | Length2=-1 9 | File3=http://91.121.174.35:8000 10 | Title3=(#3 - 1582/3000) NOISEfm.pl - Niezalezne Radio Rock ! 11 | Length3=-1 12 | Version=2 13 | -------------------------------------------------------------------------------- /test/extended.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:822,Iron Maiden - Rime of the Ancient Mariner 3 | ~/Music/RotAM.mp3 4 | #EXTINF:272,Katatonia - Burn the Remembrance 5 | mp3/katatonia/burn_the_remembrance.ogg 6 | #EXTINF:381,Tool - The Pot 7 | http://stolenmp3s.com/12345.m4a 8 | #EXTINF:249,A Perfect Circle - Passive 9 | ws://stolenmp3s.com/54321.aac 10 | #EXTINF:249,Entwine - Save Your Sins 11 | wss://192.168.1.1/llllllll.mp3 12 | 13 | -------------------------------------------------------------------------------- /test/malformed.asx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 1 4 | 6 | 7 | 8 | Item 2 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/malformed_no_attributes.asx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 1 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/malformed_wrong_case.asx: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 1 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/mixed.m3u8: -------------------------------------------------------------------------------- 1 | # Comments start with a pound sign 2 | # http://en.wikipedia.org/wiki/M3U 3 | 4 | # Absolute 5 | /Users/Nicholas/Music/lateralus_clip.ogg 6 | 7 | # Relative 8 | ../../Music/lateralus_clip.mp3 9 | 10 | # URL 11 | http://lostoracle.net:3000/transcode/ogg/4194855 12 | http://lostoracle.net:3000/transcode/ogg/4196307 13 | http://lostoracle.net:3000/transcode/ogg/4196307 14 | 15 | -------------------------------------------------------------------------------- /test/negative_time.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1,SWR4 Rheinland-Pfalz 3 | http://swr-mp3-m-swr4rp.akacast.akamaistream.net/7/906/137138/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr4rp 4 | 5 | -------------------------------------------------------------------------------- /test/pound_hash_name.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:822,Iron Maiden - R#ime of the Ancient Mariner 3 | ~/Music/RotAM.mp3 4 | 5 | -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | should = require('chai').should() 3 | expect = require('chai').expect 4 | 5 | parsers = require '../lib/parser.min.js' 6 | M3U = parsers.M3U 7 | PLS = parsers.PLS 8 | ASX = parsers.ASX 9 | 10 | describe 'm3u parsing', -> 11 | it 'should have a name of m3u', -> 12 | M3U.name.should.equal 'm3u' 13 | 14 | it 'should return an array', -> 15 | parsed = M3U.parse '' 16 | parsed.should.be.an 'array' 17 | parsed.length.should.be.empty 18 | 19 | describe 'default parsing', -> 20 | it 'should return an array of objects', -> 21 | playlist = fs.readFileSync './test/url.m3u', encoding: 'utf8' 22 | parsed = M3U.parse playlist 23 | parsed.should.be.an 'array' 24 | parsed.should.not.be.empty 25 | parsed.length.should.equal 2 26 | 27 | parsed.forEach (song) -> 28 | song.should.be.an 'object' 29 | song.should.have.ownProperty 'file' 30 | 31 | parsed[0].file.should.equal 'http://stream-sd.radioparadise.com:8058' 32 | parsed[1].file.should.equal 'http://stream-sd.radioparadise.com:8056' 33 | 34 | describe 'extended parsing', -> 35 | it 'should return an array of objects', -> 36 | playlist = fs.readFileSync './test/extended.m3u', encoding: 'utf8' 37 | parsed = M3U.parse playlist 38 | parsed.length.should.equal 5 39 | parsed.forEach (song) -> 40 | song.should.have.ownProperty 'length' 41 | song.should.have.ownProperty 'artist' 42 | song.should.have.ownProperty 'title' 43 | song.should.have.ownProperty 'file' 44 | 45 | describe 'bad extended', -> 46 | it 'should revert to default parsing from extended parsing', -> 47 | playlist = fs.readFileSync './test/bad_extended.m3u', encoding: 'utf8' 48 | parsed = M3U.parse playlist 49 | parsed.length.should.equal 1 50 | expect(parsed[0]).not.to.be.undefined 51 | parsed[0].file.should.equal 'http://radio.4duk.ru:80/4duk40.mp3' 52 | 53 | playlist2 = fs.readFileSync './test/bad_extended2.m3u', encoding: 'utf8' 54 | parsed2 = M3U.parse playlist2 55 | parsed2.length.should.equal 1 56 | expect(parsed2[0]).not.to.be.undefined 57 | 58 | playlist3 = fs.readFileSync './test/bad_extended3.m3u', encoding: 'utf8' 59 | parsed3 = M3U.parse playlist3 60 | parsed3.length.should.equal 1 61 | expect(parsed3[0]).not.to.be.undefined 62 | 63 | describe 'negative time', -> 64 | it 'should still parse', -> 65 | playlist = fs.readFileSync './test/negative_time.m3u', encoding: 'utf8' 66 | parsed = M3U.parse playlist 67 | parsed.length.should.equal 1 68 | parsed[0].should.be.an 'object' 69 | parsed[0].should.have.ownProperty 'length' 70 | parsed[0].length.should.equal '-1' 71 | 72 | describe 'titles containing a pound or hash character', -> 73 | it 'should still parse', -> 74 | playlist = fs.readFileSync './test/pound_hash_name.m3u', encoding: 'utf8' 75 | parsed = M3U.parse playlist 76 | parsed.length.should.equal 1 77 | parsed[0].title.should.equal 'R#ime of the Ancient Mariner' 78 | 79 | describe 'pls parsing', -> 80 | it 'should have a name of pls', -> 81 | PLS.name.should.equal 'pls' 82 | 83 | it 'should return an array', -> 84 | parsed = PLS.parse '' 85 | parsed.should.be.an 'array' 86 | parsed.length.should.be.empty 87 | 88 | it 'should return an array of objects', -> 89 | parsed = PLS.parse fs.readFileSync './test/example.pls', encoding: 'utf8' 90 | parsed.length.should.equal 3 91 | parsed.forEach (song) -> 92 | song.should.be.an 'object' 93 | song.should.have.ownProperty 'file' 94 | song.should.have.ownProperty 'title' 95 | song.should.have.ownProperty 'length' 96 | 97 | describe 'asx parsing', -> 98 | describe 'well formed', -> 99 | it 'should have a name of asx', -> 100 | ASX.name.should.equal 'asx' 101 | 102 | it 'should return an array', -> 103 | parsed = ASX.parse '' 104 | parsed.should.be.an 'array' 105 | parsed.length.should.be.empty 106 | 107 | it 'should return an array of objects', -> 108 | parsed = ASX.parse fs.readFileSync './test/example.asx', encoding: 'utf8' 109 | parsed.length.should.equal 1 110 | parsed[0].file.should.equal 'http://kexp-mp3-2.cac.washington.edu:8000/' 111 | 112 | describe 'malformed', -> 113 | it 'should still parse', -> 114 | playlist = fs.readFileSync './test/malformed.asx', encoding: 'utf8' 115 | parsed = ASX.parse playlist 116 | parsed.length.should.equal 2 117 | parsed[0].file.should.equal 'http://stream.radiotime.com/sample.mp3' 118 | parsed[1].file.should.equal 'http://kexp-mp3-2.cac.washington.edu:8000/' 119 | 120 | it 'should still parse, for wrong case attributes', -> 121 | parsed = ASX.parse fs.readFileSync './test/malformed_wrong_case.asx', encoding: 'utf8' 122 | parsed.length.should.equal 1 123 | parsed[0].file.should.equal 'http://kexp-mp3-2.cac.washington.edu:8000/' 124 | 125 | it 'should return null, when REF has no attributes', -> 126 | parsed = ASX.parse fs.readFileSync './test/malformed_no_attributes.asx', encoding: 'utf8' 127 | parsed.should.be.an 'array' 128 | parsed.length.should.be.empty 129 | 130 | -------------------------------------------------------------------------------- /test/url.m3u: -------------------------------------------------------------------------------- 1 | http://stream-sd.radioparadise.com:8058 2 | http://stream-sd.radioparadise.com:8056 3 | --------------------------------------------------------------------------------