eshaz / icecast-metadata-js

Browser and NodeJS packages for playing and reading Icecast compatible streaming audio with realtime metadata updates.
164 stars 20 forks source link

Writing metadata to a file #196

Closed albertojorge1983 closed 9 months ago

albertojorge1983 commented 9 months ago

Awesome job by the way. You are making my life way easier ;).

This is the way I'm trying to implement the IcecastMetadataStream class, but I'm getting that weird error at the top. This is the implementation. Maybe Im missing something but I'm kind of stuck in here:

fetch(this.config.url, {
      method: 'GET',
      headers: {
        'Icy-MetaData': '1'
      }
    }).then(async (res) => {
      console.log(res.headers)

      const module = await import('icecast-metadata-js')
      const IcecastMetadataStream = module.IcecastMetadataStream

      const icecastStream = new IcecastMetadataStream({
        icyMetaInt: parseInt(res.headers['icy-metaint']),
        mimeType: res.headers['content-type']
      })

      const streamWriteFile = fs.createWriteStream(`${this.pathLogs}/${this.config.session_id.toString()}.mp3`)
      const metadataWriteFile = fs.createWriteStream(`${this.pathLogs}/${this.config.session_id.toString()}.txt`)

      icecastStream.stream.pipe(streamWriteFile);
      icecastStream.metadata.pipe(metadataWriteFile)

      res.body.pipe(icecastStream) 

    })
    .catch((e) => {
      console.log(e)
    })

and here u can see the whole error. It is specific when piping icecastStream.metadata, the icecastStream.stream pipe works great.

node:internal/streams/writable:314
      throw new ERR_INVALID_ARG_TYPE(
      ^

TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object
    at new NodeError (node:internal/errors:387:5)
    at _write (node:internal/streams/writable:314:13)
    at WriteStream.Writable.write (node:internal/streams/writable:336:10)
    at PassThrough.ondata (node:internal/streams/readable:754:22)
    at PassThrough.emit (node:events:513:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at PassThrough.Readable.push (node:internal/streams/readable:228:10)
    at IcecastMetadataStream._handleMetadata (file:///Users/adujarric/Documents/icecast-parser/node_modules/icecast-metadata-js/src/IcecastMetadataStream.js:95:20)
    at IcyMetadataParser.onMetadata [as _onMetadata] (file:///Users/adujarric/Documents/icecast-parser/node_modules/icecast-metadata-js/src/IcecastMetadataStream.js:74:35) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Thanks in advance and look forward to your help.

eshaz commented 9 months ago

The metadata stream is sending the data as a stream of objects rather than as strings or buffers. If you want to save the metadata into a file, you'll need to serialize the objects before writing to the file. You can do this by creating a transform stream that takes in the metadata objects, serializes using JSON.stringify (or some other format if you prefer), and outputs the string.

albertojorge1983 commented 9 months ago

Great thanks. I did something like this and it works like charm:

transform () {
    return new Transform({
      objectMode: true,
      transform(chunk, encoding, callback) {
        try {
          callback(null, JSON.stringify(chunk))
        } catch (error) {
          callback(error)
        }
      }
    })
  }```

and then I pipe to it like this and just works:

```javascript
icecastStream.metadata.pipe(this.transform()).pipe(metadataWriteFile)

Thank you so much. Another thing that I can't make work are both callbacks. Maye you can suggest something about it. This is the way I was trying to implement those but are not printing a thing, so basically they not been called:

    const u = url.parse(this.config.url, true)

    const options = {
      hostname: u.hostname,
      path: u.path,
      headers: {
        'Icy-MetaData': 1
      }
    }

    this.req = https.get(options, async (response) => {
      console.log(response.headers)

      if (this.config.record) {
        await Headers.insertOne({
          session_id: this.config.session_id,
          stream_id: this.config.stream_id,
          meta: response.headers
        })

        // Check if directory exist with stream_id as name if it doesn't, then create it
        if (!fs.existsSync(this.pathLogs)) {
          fs.mkdirSync(this.pathLogs, { recursive: true })
        }
      }

      const module = await import('icecast-metadata-js')
      const IcecastMetadataStream = module.IcecastMetadataStream

      const options = {
        icyMetaInt: parseInt(response.headers['icy-metaint']),
        mimeType: response.headers['content-type'],
        onStream: (value) => {
          console.log('onStream')
          console.log(value)
        },
        onMetadata: (value) => {
          console.log('onMetadata')
          console.log(value)
        }
      }

      const icecastStream = new IcecastMetadataStream(options)

      const streamWriteFile = fs.createWriteStream(`${this.pathLogs}/${this.config.session_id.toString()}.mp3`)
      const metadataWriteFile = fs.createWriteStream(`${this.pathLogs}/${this.config.session_id.toString()}.txt`)

      icecastStream.stream.pipe(streamWriteFile)
      icecastStream.metadata.pipe(this.transform()).pipe(metadataWriteFile)

      response.pipe(icecastStream) 
    })

    this.req.on('error', (err) => {
      console.log("Error: ", err)
    })

    this.req.end()

Both callback onMetadata and onStream are not getting called. Maybe I'm missing something. Thank u so much for your help. Again, great work at this packages. Look forward to hear from you.