Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
feat: add methods to work with stream
Browse files Browse the repository at this point in the history
  • Loading branch information
sheverniskiy committed Jun 10, 2022
1 parent 115f42d commit 5c99cf0
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 8 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ wasd.on('message', (ctx) => {
})
```

### Capturing Stream

Yes! You can capture stream (for example, into file)

```ts
import { WasdTv } from 'wasdtv'
import fs from 'fs'

const wasd = new WasdTv('YOUR_API_TOKEN')

// Get stream metadata
wasd.getMediaStreamMetadata(1328329).then((data) => {
console.log(data)
})

const media = wasd.getMediaStream(1328329)
media.pipe(fs.createWriteStream('video.mp4'))

// Capture 20s of stream and close connection
setTimeout(() => {
media.end()
}, 20 * 1000)
```

## Warning

Due to the fact that Wasd has almost no adequate api documentation, typing was done by reversing responses. For this reason types can be incorrect and incomplete.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
},
"dependencies": {
"axios": "^0.27.2",
"m3u8stream": "^0.8.6",
"socket.io-client": "^2.4.0"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default [
format: 'esm',
},
plugins: [typescript()],
external: ['axios', 'socket.io-client', 'events'],
external: ['axios', 'socket.io-client', 'events', 'm3u8stream'],
},
{
input: './src/wasdtv.ts',
Expand All @@ -16,6 +16,6 @@ export default [
format: 'cjs',
},
plugins: [typescript()],
external: ['axios', 'socket.io-client', 'events'],
external: ['axios', 'socket.io-client', 'events', 'm3u8stream'],
},
]
66 changes: 65 additions & 1 deletion src/clients/rest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios, { AxiosInstance, AxiosError } from 'axios'
import m3u8stream from 'm3u8stream'
import { PassThrough } from 'stream'

import { HttpError, ApiError } from '../core/error'
import { Wasd } from '../types/api'
Expand Down Expand Up @@ -223,7 +225,69 @@ export default class RestClient {
.catch((err) => this.errorHandler(err))
}

private errorHandler(err: AxiosError) {
public getMediaStream(stream_id: number): PassThrough {
return m3u8stream(`https://cdn-curie.wasd.tv/live/${stream_id}/tracks-v1a1/mono.m3u8`)
}

public async getMediaStreamMetadata(stream_id: number): Promise<Wasd.MediaStreamMetadata> {
return axios
.get(`https://cdn.wasd.tv/live/${stream_id}/index.m3u8`)
.then(({ data }) => {
const parse = {
bandwidth: data
.match(/(,BANDWIDTH=[0-9]*)/gm)
?.at(0)
?.replace(',BANDWIDTH=', ''),
average_bandwidth: data
.match(/(,AVERAGE-BANDWIDTH=[0-9]*)/gm)
.at(0)
?.replace(',AVERAGE-BANDWIDTH=', ''),
codecs: data
.match(/(,CODECS=".*")/gm)
?.at(0)
?.replace(',CODECS="', '')
.replace('"', ''),
resolution: data
.match(/(,RESOLUTION=[0-9]*x[0-9]*)/gm)
?.at(0)
?.replace(',RESOLUTION=', ''),
closed_captions: data
.match(/(,CLOSED-CAPTIONS=[A-Z]*)/gm)
?.at(0)
?.replace(',CLOSED-CAPTIONS=', ''),
frame_rate: data
.match(/(,FRAME-RATE=[0-9]*\.[0-9]*)/gm)
?.at(0)
?.replace(',FRAME-RATE=', ''),
source_url: data.match(/(https:\/\/.*m3u8)/gm)?.at(0),
}

return {
bandwidth: parse.bandwidth ? Number(parse.bandwidth) : undefined,
average_bandwidth: parse.average_bandwidth ? Number(parse.average_bandwidth) : undefined,
codecs: parse.codecs,
resolution: parse.resolution,
closed_captions: parse.closed_captions,
frame_rate: parse.frame_rate ? Number(parse.frame_rate) : undefined,
source_url: parse.source_url,
}
})
.catch((err) => {
this.errorHandler(err)
return {
bandwidth: undefined,
average_bandwidth: undefined,
codecs: undefined,
resolution: undefined,
closed_captions: undefined,
frame_rate: undefined,
source_url: undefined,
}
})
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private errorHandler(err: any) {
if (err instanceof AxiosError) {
if (err.response?.data?.error !== undefined) {
throw new ApiError(JSON.stringify(err.response.data.error, null, 2))
Expand Down
16 changes: 13 additions & 3 deletions src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,14 +462,14 @@ export namespace Wasd {
channel_alias: null
channel_priority: number
last_activity_date: Date
meta: MetaMediaContainer
meta: MediaContainerMeta
}

export interface MetaMediaContainer {}
export interface MediaContainerMeta {}

// TODO: parse all this
export type MediaContainerOnlineStatus = 'PUBLIC'
export type MediaStatus = 'RUNNING'
export type MediaStatus = 'RUNNING' | 'STOPPED'
export type MediaType = 'HLS'
export type MediaContainerType = 'SINGLE'
export type TagType = 'DEFAULT' | 'RECOMMENDATION' | 'WARNING'
Expand Down Expand Up @@ -504,6 +504,16 @@ export namespace Wasd {
sticker: Wasd.ChatSticker
}

export interface MediaStreamMetadata {
bandwidth?: number
average_bandwidth?: number
codecs?: string
resolution?: string
closed_captions?: string
frame_rate?: number
source_url?: string
}

export enum ChatPermission {
ALL = '0',
ONLY_FOLLOWERS = '1',
Expand Down
13 changes: 11 additions & 2 deletions src/wasdtv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import EventEmitter from 'events'
import { PassThrough } from 'stream'

import RestClient from './clients/rest'
import SocketClient from './clients/socketio'
Expand Down Expand Up @@ -129,12 +130,20 @@ export class WasdTv extends EventEmitter {
}

public async getMediaContainers(
media_container_status?: Wasd.MediaStatus,
media_container_type?: Wasd.MediaContainerType,
media_container_status: Wasd.MediaStatus = 'RUNNING',
media_container_type: Wasd.MediaContainerType = 'SINGLE',
game_id?: number,
limit = 20,
offset = 0,
): Promise<Wasd.MediaContainerExtra[]> {
return await this._rest.getMediaContainers(media_container_status, media_container_type, game_id, limit, offset)
}

public getMediaStream(stream_id: number): PassThrough {
return this._rest.getMediaStream(stream_id)
}

public async getMediaStreamMetadata(stream_id: number): Promise<Wasd.MediaStreamMetadata> {
return await this._rest.getMediaStreamMetadata(stream_id)
}
}
18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2384,6 +2384,14 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"

m3u8stream@^0.8.6:
version "0.8.6"
resolved "https://registry.yarnpkg.com/m3u8stream/-/m3u8stream-0.8.6.tgz#0d6de4ce8ee69731734e6b616e7b05dd9d9a55b1"
integrity sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==
dependencies:
miniget "^4.2.2"
sax "^1.2.4"

make-dir@^3.0.0, make-dir@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
Expand Down Expand Up @@ -2438,6 +2446,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==

miniget@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/miniget/-/miniget-4.2.2.tgz#db20320f265efdc4c1826a0be431d56753074475"
integrity sha512-a7voNL1N5lDMxvTMExOkg+Fq89jM2vY8pAi9ZEWzZtfNmdfP6RXkvUtFnCAXoCv2T9k1v/fUJVaAEuepGcvLYA==

minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
Expand Down Expand Up @@ -2736,6 +2749,11 @@ safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==

sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==

[email protected], semver@^7.3.5, semver@^7.3.7:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
Expand Down

0 comments on commit 5c99cf0

Please sign in to comment.