radiomanifest.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const fetch = require('isomorphic-unfetch')
  2. function getStreaminfoUrl (siteurl) {
  3. return siteurl + '/streaminfo.json' // XXX: improve this logic
  4. }
  5. function getManifestUrl (siteurl) {
  6. return siteurl + '/radiomanifest.xml' // XXX: improve this logic
  7. }
  8. function getAttribute(el, attr, default_value) {
  9. if(el.hasAttribute(attr))
  10. return el.getAttribute(attr);
  11. return default_value;
  12. }
  13. class Radio {
  14. constructor (sources, schedule, shows, feed) {
  15. this.streaming = new RadioStreaming(sources)
  16. this.schedule = schedule
  17. this.shows = shows
  18. this.feed = feed
  19. this.name = ''
  20. }
  21. getStreaming () {
  22. return this.streaming
  23. }
  24. setName (name) {
  25. this.name = name
  26. }
  27. getShowByName (showName) {
  28. }
  29. getSchedule () {
  30. }
  31. getShowAtTime () {
  32. }
  33. static fromDOM (xml) {
  34. const doc = xml.cloneNode(true)
  35. let res = doc.evaluate('/radio-manifest/streaming/source', doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
  36. const sources = []
  37. for (let i = 0; i < res.snapshotLength; i++) {
  38. const src = res.snapshotItem(i)
  39. if (!src.hasAttribute('priority')) {
  40. src.setAttribute('priority', '0')
  41. } else if (parseInt(src.getAttribute('priority'), 10) < 0) {
  42. continue
  43. }
  44. sources.push(src)
  45. }
  46. res = doc.evaluate('/radio-manifest/schedule', doc)
  47. const scheduleEl = res.iterateNext()
  48. let schedule = null
  49. if (scheduleEl !== null) {
  50. schedule = scheduleEl.getAttribute('src')
  51. }
  52. res = xml.evaluate('/radio-manifest/shows', xml)
  53. const showsEl = res.iterateNext()
  54. let shows = null
  55. if (showsEl !== null) {
  56. shows = showsEl.getAttribute('src')
  57. }
  58. res = xml.evaluate('/radio-manifest/feed', xml)
  59. const feedEl = res.iterateNext()
  60. let feed = null
  61. if (feedEl !== null) {
  62. feed = feedEl.getAttribute('src')
  63. }
  64. const manifest = new Radio(sources, schedule, shows, feed)
  65. return manifest
  66. }
  67. }
  68. class RadioStreaming {
  69. constructor (sources) {
  70. this.sources = sources.sort(
  71. (a,b) => this.getPriority(a) < this.getPriority(a)
  72. )
  73. }
  74. getOptions() {
  75. return this.sources.map(function (x) {
  76. return x.getAttribute('name')
  77. })
  78. }
  79. // this is private
  80. getPriority(element) {
  81. return parseInt(getAttribute(element, 'priority', '1'))
  82. }
  83. // this is private
  84. getTopPrioritySources() {
  85. var topPriority = this.getPriority(this.sources[0])
  86. return this.sources.filter(
  87. (src) => parseInt(src.getAttribute('priority'), 10) === topPriority
  88. )
  89. }
  90. getSource(name) {
  91. if (name === undefined) {
  92. return this.getTopPrioritySources()[0]
  93. }
  94. const s = this.sources.find(function (x) {
  95. return x.getAttribute('name') === name
  96. })
  97. if (s === undefined) return s
  98. return s.getAttribute('src')
  99. }
  100. async pickURLs() {
  101. var allSources = this.getTopPrioritySources()
  102. var allAudios = []
  103. for(let src of allSources) {
  104. let url = src.getAttribute('src')
  105. let resp = await fetch(url)
  106. allAudios.unshift(... parseM3U(await resp.text()))
  107. }
  108. return allAudios
  109. }
  110. async pickURL() {
  111. var allAudios = await this.pickURLs()
  112. return allAudios[0]
  113. }
  114. }
  115. async function get (siteurl, options) {
  116. let resp = await fetch(getManifestUrl(siteurl))
  117. let text = await resp.text()
  118. const parser = new DOMParser()
  119. const dom = parser.parseFromString(text, 'text/xml')
  120. const manifest = Radio.fromDOM(dom)
  121. resp = null
  122. try {
  123. resp = await fetch(getStreaminfoUrl(siteurl))
  124. } catch (e) {
  125. true
  126. }
  127. if(resp !== null) {
  128. try {
  129. text = await resp.text()
  130. const data = JSON.parse(text)
  131. const name = data['icy-name']
  132. if (name !== undefined) {
  133. manifest.setName(name)
  134. }
  135. } catch (e) {
  136. if (e instanceof SyntaxError) {
  137. true
  138. } else {
  139. console.error('Error', e)
  140. throw e
  141. }
  142. }
  143. }
  144. // XXX: in base alle options fai fetch anche di altra roba
  145. return manifest
  146. }
  147. function parseM3U (body) {
  148. return body.split('\n').filter((line) => {
  149. if (line.startsWith('#')) {
  150. return false
  151. } else {
  152. try {
  153. new URL(line); return true
  154. } catch {
  155. return false
  156. }
  157. }
  158. })
  159. }
  160. module.exports = {
  161. get: get,
  162. objs: {
  163. Radio: Radio,
  164. RadioStreaming: RadioStreaming
  165. },
  166. parsers: {
  167. M3U: parseM3U,
  168. radioManifest: Radio.fromDOM
  169. }
  170. }