diff --git a/docusaurus/video/docusaurus/docs/api/_common_/go_live.mdx b/docusaurus/video/docusaurus/docs/api/_common_/go_live.mdx new file mode 100644 index 00000000..1c41b638 --- /dev/null +++ b/docusaurus/video/docusaurus/docs/api/_common_/go_live.mdx @@ -0,0 +1,19 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +```js +call.goLive(); +``` + + + + +```py +call.go_live() +``` + + + \ No newline at end of file diff --git a/docusaurus/video/docusaurus/docs/api/_common_/rtmp.mdx b/docusaurus/video/docusaurus/docs/api/_common_/rtmp.mdx new file mode 100644 index 00000000..7b9202d8 --- /dev/null +++ b/docusaurus/video/docusaurus/docs/api/_common_/rtmp.mdx @@ -0,0 +1,33 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + +```js +const resp = await call.getOrCreate({ data: { created_by_id: 'john' } }); +// user ID of an existing user +const userId = 'jane'; +const token = client.createToken(userId); + +const address = resp.call.ingress.rtmp.address.replace( + '', + token, +); +``` + + + + +```py +response = call.create(GetOrCreateCallRequest( + data=CallRequest( + created_by_id="sacha", + ), + ),) +rtmp_url = response.data().call.ingress.rtmp.address +``` + + + \ No newline at end of file diff --git a/docusaurus/video/docusaurus/docs/api/assets/layout_custom_options.png b/docusaurus/video/docusaurus/docs/api/assets/layout_custom_options.png new file mode 100644 index 00000000..e5cd4604 Binary files /dev/null and b/docusaurus/video/docusaurus/docs/api/assets/layout_custom_options.png differ diff --git a/docusaurus/video/docusaurus/docs/api/assets/layout_grid.png b/docusaurus/video/docusaurus/docs/api/assets/layout_grid.png new file mode 100644 index 00000000..c7b6d81f Binary files /dev/null and b/docusaurus/video/docusaurus/docs/api/assets/layout_grid.png differ diff --git a/docusaurus/video/docusaurus/docs/api/assets/layout_single-participant.png b/docusaurus/video/docusaurus/docs/api/assets/layout_single-participant.png new file mode 100644 index 00000000..b0bfcc8e Binary files /dev/null and b/docusaurus/video/docusaurus/docs/api/assets/layout_single-participant.png differ diff --git a/docusaurus/video/docusaurus/docs/api/assets/layout_spotlight.png b/docusaurus/video/docusaurus/docs/api/assets/layout_spotlight.png new file mode 100644 index 00000000..89ec2cc7 Binary files /dev/null and b/docusaurus/video/docusaurus/docs/api/assets/layout_spotlight.png differ diff --git a/docusaurus/video/docusaurus/docs/api/basics/authentication.mdx b/docusaurus/video/docusaurus/docs/api/basics/authentication.mdx index c1d9f0de..5358853d 100644 --- a/docusaurus/video/docusaurus/docs/api/basics/authentication.mdx +++ b/docusaurus/video/docusaurus/docs/api/basics/authentication.mdx @@ -10,18 +10,24 @@ import TabItem from '@theme/TabItem'; ## Creating users +Stream Users require only an id to be created. Users can be created with role of user or admin. The role will be set to user if a value is not provided in the request. There are additional properties you can provide to further describe your users. + +The `name` and `image` fields are special fields that are supported by client-side SDKs. + +You can provide additional data for the user object using the `custom` field. + ```js const newUser: UserObjectRequest = { id: 'userid', - role: "user", + role: 'user', custom: { - color: 'red' + color: 'red', }, name: 'This is a test user', - image: 'link/to/profile/image' + image: 'link/to/profile/image', }; await client.upsertUsers({ users: { @@ -35,18 +41,23 @@ await client.upsertUsers({ ## Updating users +There are two ways to update user objects: + +- Updating will replace the existing user object +- Partial update will let you choose which fields you want to chage/unset + ```js const user: UserObjectRequest = { id: 'userid', - role: "user", + role: 'user', custom: { - color: 'red' + color: 'red', }, name: 'This is a test user', - image: 'link/to/profile/image' + image: 'link/to/profile/image', }; client.upsertUsers({ users: { @@ -55,22 +66,71 @@ client.upsertUsers({ }); // or -client.updateUsersPartial({users: [ - { - id: user.id, - set: { - color: 'blue' - }, - unset: ['name'], - } -]}); +client.updateUsersPartial({ + users: [ + { + id: user.id, + set: { + color: 'blue', + }, + unset: ['name'], + }, + ], +}); ``` +## Anonymous users + +Anonymous users are users that are not authenticated. It's common to use this for watching a livestream or similar where you aren't authenticated. Anonymous users can be connected using client-side SDKs. + +## Guest users + +Guest users are temporary user accounts. You can use it to temporarily give someone a name and image when joining a call. Guest users can aslso be created cliend-side. + + + + +```js +const guest: UserObjectRequest = { + id: '', + name: '', + custom: { + color: 'red', + }, +}; + +const guest = (await client.createGuest({ user: guest })).user; +``` + + + + +## Deactivating and deleting users + +While it is usually safer for data retention to deactivate a user, some use cases require completely deleting a user and their data. + +Once a user has been deleted, it cannot be un-deleted and the user_id cannot be used again. + +```js +client.deactivateUser({ + user_id: '', +}); + +client.deleteUser({ userId: '' }); +``` + ## User tokens +Stream uses JWT (JSON Web Tokens) to authenticate chat users, enabling them to login. Knowing whether a user is authorized to perform certain actions is managed separately via a role based permissions system. Tokens need to be generated server-side. + +You can optionally provide + +- expiration time +- issued at date which is necessary if you manually wan to revoke tokens + @@ -87,33 +147,17 @@ client.createToken(userId, exp, iat); ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") +import time - if apiKey == "" || apiSecret == "" || userID == "" { - panic("API_KEY, API_SECRET and USER_ID env variables must be set") - } +# user ID +user_id = 'john' - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) +# exp and iat are optional +# the token will be valid for 1 hour +exp = int(time.time()) + 60 * 60 +iat = int(time.time()) - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - fmt.Println("token ->", token) -} +client.create_token(user_id = user_id, exp = exp, iat = iat) ``` @@ -121,6 +165,8 @@ func main() { ## Call tokens +Call tokens contain a list of call IDs. If a user is authenticated with a call token, they can only access the specified calls. + @@ -133,40 +179,29 @@ const iat = Math.round(new Date().getTime() / 1000); const call_cids = ['default:call1', 'livestream:call2']; -client.createToken(userId, exp, iat, call_cids); +client.createToken( + (user_id = user_id), + (exp = exp), + (iat = iat), + (call_cids = call_cids), +); ``` ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") +import time - if apiKey == "" || apiSecret == "" || userID == "" { - panic("API_KEY, API_SECRET and USER_ID env variables must be set") - } +user_id = 'john' - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) +# exp and iat are optional, token will be valid for 1 hour +exp = int(time.time()) + 60 * 60 +iat = int(time.time()) - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } +call_cids = ['default:call1', 'livestream:call2'] - fmt.Println("token ->", token) -} +client.create_token(user_id=user_id, exp, iat, call_cids) ``` @@ -174,5 +209,16 @@ func main() { ## Token expiration and token providers -TODO: explain that tokens should have an expiration -TODO: mention that in a production environment you have an endpoint to send fresh tokens to users +At a high level, the Token Provider is an endpoint on your server that can perform the following sequence of tasks: + +1. Receive information about a user from the front end. + +2. Validate that user information against your system. + +3. Provide a User-ID corresponding to that user to the server client's token creation method. + +4. Return that token to the front end. + +Tokens can only be safely generated from a server. This means you will need to implement a Token Provider prior to deploying your application to production. To conduct development before implementing a Token Provider, you will need to disable token authentication. + +Tokens also should have an expiration date to ensure adequate security. Once a token is expired, cliend-side SDKs can automatically call an endpoint to refresh the token, this endpoint should also be part of the Token Provider implementation. diff --git a/docusaurus/video/docusaurus/docs/api/basics/calls.mdx b/docusaurus/video/docusaurus/docs/api/basics/calls.mdx index 308d1573..bf2246a6 100644 --- a/docusaurus/video/docusaurus/docs/api/basics/calls.mdx +++ b/docusaurus/video/docusaurus/docs/api/basics/calls.mdx @@ -10,6 +10,13 @@ import TabItem from '@theme/TabItem'; ## Creating calls +You can create a call by providing the call type and an ID: + +- The [call type](call_types/builtin) controls which features are enabled, and sets up permissions. +- Calls IDs can be reused, which means they can be joined multiple times, so it's possible to set up recurring calls. + +You can optionally restrict call access by providing a list of users. + @@ -35,41 +42,27 @@ call.create({ ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - fmt.Println("response", response) -} +# create a call +call.get_or_create_call( + data=CallRequest( + created_by_id='john' + ) +) +# create a call with more data +call.get_or_create_call( + data=CallRequest( + created_by_id='john', + members=[ + MemberRequest( + user_id: 'john', + role: 'admin' + ), + MemberRequest( + user_id: 'jack' + ) + ] + ), +) ``` @@ -94,11 +87,18 @@ curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}?api_ ## Updating calls +- Most of the call type settings can be overridden on a call level. +- Custom data can also be stored for calls. + ```js -call.update({ settings_override: { audio: { mic_default_on: true, default_device: "speaker" } } }); +call.update({ + settings_override: { + audio: { mic_default_on: true, default_device: 'speaker' }, + }, +}); // or to update custom data call.update({ custom: { color: 'red' } }); @@ -108,56 +108,19 @@ call.update({ custom: { color: 'red' } }); ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - - // get an already created call or create one - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - updateResponse, err := client.UpdateCall("default", callID, &models.UpdateCallRequest{ - Type: "default", - ID: callID, - Custom: nil, - SettingsOverride: &models.CallSettingsRequest{ - Video: &models.VideoSettingsRequest{Enabled: PtrTo(true)}, - }, - StartsAt: nil, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", updateResponse) -} +# update call settings +call.update_call( + settings_override=CallSettingsRequest( + audio=AudioSettingsRequest( + mic_default_on=True, + ), + ), +) + +# update call with custom data +call.update_call( + custom={'color': 'red'} +) ``` @@ -182,6 +145,8 @@ curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}?api_k ## Manage call members +Call members can be added and removed as necessary. Their role's can also be changed. + @@ -201,51 +166,22 @@ call.updateCallMembers({ ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - - // get an already created call or create one - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - updateResponse, err := client.UpdateMembers("default", id, &models.UpdateCallMembersRequest{ - Type: "default", ID: id, - UpdateMembers: []models.MemberRequest{{UserId: "federico_guerinoni@getstream_io"}}, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", updateResponse) -} +# update or add call members + +call.update_call( + members=[ + MemberRequest(user_id: 'sara'), + MemberRequest(user_id: 'jack', role: 'admin') + ] +) + +# remove call members +# Assuming the updated call members are 'sara' and 'jack' +call.update_call( + members=[ + MemberRequest(user_id: 'jack', role: 'admin') + ] +) ``` @@ -268,6 +204,14 @@ curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}/membe ## Query calls +For many video calling, live stream, or audio rooms apps, you'll want to show: + +- Upcoming calls +- Calls that are currently live +- Popular live streams / audio rooms with a link to the recording + +Below you can find a few examples of different quieries: + @@ -294,9 +238,38 @@ client.video.queryCalls({ }); ``` + + + + +```py +# default sorting +call.query_calls(data=QueryCallsRequest()) + +# sorting and pagination +response = call.query_calls( + sort= [SortParamRequest(field: 'starts_at', direction: -1)], + limit=2, +) + +# loading next page +call.query_calls( + sort= [SortParamRequest(field: 'starts_at', direction: -1)], + limit=2, + next=response.data().next +) + +# filtering +call.query_calls( + filter_conditions={'backstage': {'$eq': False}} +) +``` + +Filter expressions support multiple match criteria and it's also possible to combine filters. For more information visit the [filter operators](https://getstream.io/chat/docs/node/query_syntax_operators/?language=javascript) guide. + ## Query call members @@ -325,5 +298,34 @@ call.queryMembers({ }); ``` + + + + +```py +# default sorting +call.query_members() + +# sorting and pagination +response = call.query_members( + sort: [SortParamRequest(field: "user_id", direction: 1)], + limit: 2, +) + +# loading next page +call.query_members( + sort: [SortParamRequest(field: "user_id", direction: 1)], + limit: 2, + next: response.next, +) + +# filtering +call.query_members( + filter_conditions: {"role": {"$eq": "admin"}}, +) +``` + + +Filter expressions support multiple match criteria and it's also possible to combine filters. For more information visit the [filter operators](https://getstream.io/chat/docs/node/query_syntax_operators/?language=javascript) guide. diff --git a/docusaurus/video/docusaurus/docs/api/basics/get_started.mdx b/docusaurus/video/docusaurus/docs/api/basics/get_started.mdx index 2f47bdf5..d72021f7 100644 --- a/docusaurus/video/docusaurus/docs/api/basics/get_started.mdx +++ b/docusaurus/video/docusaurus/docs/api/basics/get_started.mdx @@ -7,9 +7,18 @@ title: Get started import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import RTMP from '../_common_/rtmp.mdx'; + +For the average Stream integration, the development work focuses on code that executes in the client. However, some tasks must be executed from the server for safety (for example token generation). + +Stream provides server-side SDKs to help executing these tasks. + +You can reference our [development roadmap](https://github.com/GetStream/protocol/discussions/177) to know which languages and features are supported. ## Installation +All official SDK are available on package managers, full source code is available on the GetStream Github organization. + @@ -23,14 +32,7 @@ yarn add @stream-io/node-sdk ```bash -pip install ... -``` - - - - -```bash -go get github.com/GetStream/video-go-sdk +pip install getstream ``` @@ -38,6 +40,8 @@ go get github.com/GetStream/video-go-sdk ## Creating client +To create a server-side client you'll need your **API key** and **secret**, both of them can be found in your [Stream Dashboard](https://dashboard.getstream.io/). + @@ -51,22 +55,9 @@ client = new StreamClient(apiKey, secret); ```py -TODO -``` - - - +from getstream import Stream -```go - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) +client = Stream(api_key="your_api_key", api_secret="your_api_secret") ``` @@ -74,6 +65,8 @@ TODO ## Creating user tokens +Tokens need to be generated server-side. Typically you integrate this into the part of your codebase where you login or register users. The tokens provide a way to authenticate a user or give access to a specific set of video/audio calls. + @@ -95,39 +88,20 @@ client.createToken(userId, exp, iat, call_cids); ```py -def hello_world(): - print("Hello, world!") +token = client.create_token("admin-user") ``` - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") + - if apiKey == "" || apiSecret == "" || userID == "" { - panic("API_KEY, API_SECRET and USER_ID env variables must be set") - } +## Creating a call - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) +You can create a call by providing the call type and an ID: - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } +- The [call type](call_types/builtin) controls which features are enabled, and sets up permissions. +- Calls IDs can be reused, which means they can be joined multiple times, so it's possible to set up recurring calls. - fmt.Println("token ->", token) -} -``` - - - - -## Creating a call +You can optionally restrict call access by providing a list of users. @@ -152,41 +126,14 @@ call.create({ ```py -def hello_world(): - print("Hello, world!") -``` +from getstream.models.call_request import CallRequest - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - fmt.Println("response", response) -} +call = client.video.call("default", "id") +response = call.get_or_create_call( + data=CallRequest( + created_by_id="sacha", + ), +) ``` @@ -224,51 +171,9 @@ call.updateCallMembers({ ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - - // get an already created call or create one - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - updateResponse, err := client.UpdateMembers("default", id, &models.UpdateCallMembersRequest{ - Type: "default", ID: id, - UpdateMembers: []models.MemberRequest{{UserId: "federico_guerinoni@getstream_io"}}, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", updateResponse) -} +call.update_call_members(update_members=[ + MemberRequest(user_id: "sara"), + MemberRequest(user_id: "emily", role: "admin")]) ``` @@ -291,11 +196,19 @@ curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}/membe ## Updating a call +- Most of the call type settings can be overriden on a call level. + +- Custom data can also be stored for calls. + ```js -call.update({ settings_override: { audio: { mic_default_on: true, default_device: "speaker" } } }); +call.update({ + settings_override: { + audio: { mic_default_on: true, default_device: 'speaker' }, + }, +}); // or to update custom data call.update({ custom: { color: 'red' } }); @@ -305,56 +218,17 @@ call.update({ custom: { color: 'red' } }); ```py -def hello_world(): - print("Hello, world!") -``` +call.update( +settings_override=CallSettingsRequest( + audio=AudioSettingsRequest( + mic_default_on=True, + default_device="speaker", + ), +) +) - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - - // get an already created call or create one - id := fmt.Sprintf("call-%d", rand.Int()) - response, err := client.GetOrCreateCall("default", id, &models.GetOrCreateCallRequest{}) - if err != nil { - panic(err) - } - - updateResponse, err := client.UpdateCall("default", callID, &models.UpdateCallRequest{ - Type: "default", - ID: callID, - Custom: nil, - SettingsOverride: &models.CallSettingsRequest{ - Video: &models.VideoSettingsRequest{Enabled: PtrTo(true)}, - }, - StartsAt: nil, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", updateResponse) -} +// or to update custom data +call.update(custom: { 'color': 'red' }); ``` @@ -379,6 +253,17 @@ curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}?api_k ## Start HLS broadcasting +Broadcasting serves are a means of transmitting live or pre-recorded content to a wide audience. + +We can choose from two approaches to broadcasting the media: + +- [HLS](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) - slight delay, better buffering +- [WebRTC](https://webrtc.org/) - lower latency, less reliability + +It is up to the integrators to decide, what approach will be used in their apps for the audience to consume the streams. + +For more information see the [Streaming section](streaming/overview/). + @@ -393,52 +278,10 @@ call.stopHLSBroadcasting(); ```py -def hello_world(): - print("Hello, world!") -``` +call.start_broadcasting() - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - - // get an already created call or create one - id := fmt.Sprintf("call-%d", rand.Int()) - callResponse, err := client.JoinCall(models.CallTypeDefault, callID, &models.JoinCallRequest{ - Type: "default", ID: callID, Location: "AMS", Create: PtrTo(true), - }) - if err != nil { - panic(err) - } - - updateResponse, err := client.StartBroadcasting("default", callID, &models.StartBroadcastingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", updateResponse) -} +// to end broadcasting +call.stop_broadcasting() ``` @@ -454,44 +297,15 @@ curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}/star ## Set up a call for RTMP - - - -```js -const resp = await call.getOrCreate({ data: { created_by_id: 'john' } }); -// user ID of an existing user -const userId = 'jane'; -const token = client.createToken(userId); - -const address = resp.call.ingress.rtmp.address.replace( - '', - token, -); -``` - - - +Almost all livestream software and hardware supports RTMPS. Our API supports using a third-party software for streaming using RTMPS. For more information reference the [Streaming section](streaming/overview/). -```py -def hello_world(): - print("Hello, world!") -``` + - - - -```go -callResponse, err := client.GetOrCreateCall(models.CallTypeDefault, callID, &models.GetOrCreateCallRequest{}) -if err != nil { - panic(err) -} -addressToUse := callResponse.Call.Ingress.Rtmp.Address -``` +## Start call recording - - +Calls can be recorded for later use. Calls recording can be started/stopped via API calls or configured to start automatically when the first user joins the call. -## Start call recording +For more information see the [Recordings section](recording/calls/). @@ -507,50 +321,10 @@ call.stopRecording(); ```py -def hello_world(): - print("Hello, world!") -``` +call.start_recording() - - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } - - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - startResponse, err := client.StartRecording(models.CallTypeDefault, callID, &models.StartRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } - fmt.Println("response", startResponse) - - stopResponse, err := client.StopRecording(models.CallTypeDefault, callID, &models.StopRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } - - fmt.Println("response", stopResponse) -} +// to stop recording +call.stop_recording() ``` @@ -577,15 +351,7 @@ call.listRecordings(); ```py -def hello_world(): - print("Hello, world!") -``` - - - - -```go -TODO +recordings = call.list_recordings(session="your_session_id") ``` diff --git a/docusaurus/video/docusaurus/docs/api/recording/composite.mdx b/docusaurus/video/docusaurus/docs/api/recording/composite.mdx deleted file mode 100644 index 38099e14..00000000 --- a/docusaurus/video/docusaurus/docs/api/recording/composite.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -id: recording_composite -sidebar_position: 2 -slug: /recording/composite -title: Composite recording ---- - -## Built-in composite layouts - -## Configure composite layouts with extra options - -## Custom composite layout - diff --git a/docusaurus/video/docusaurus/docs/api/recording/recording_calls.mdx b/docusaurus/video/docusaurus/docs/api/recording/recording_calls.mdx index 378f7050..b10b45b0 100644 --- a/docusaurus/video/docusaurus/docs/api/recording/recording_calls.mdx +++ b/docusaurus/video/docusaurus/docs/api/recording/recording_calls.mdx @@ -8,8 +8,102 @@ title: Recording calls import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +Calls can be recorded for later use. Calls recording can be started/stopped via API calls or configured to start automatically when the first user joins the call. +Call recording is done by Stream server-side and later stored on AWS S3, you can also configure your Stream application to have files stored on your own S3 bucket (in that case storage costs will not apply). + +By default, calls will be recorded as mp4 video files, you can configure recording to only capture the audio. + +Note: by default recordings contain all tracks mixed in a single file, you can follow the discussion [here](https://github.com/GetStream/protocol/discussions/247) if you are interested in different ways to record calls. + +## Start and stop call recording + + + + +```js +// starts recording +call.startRecording(); + +// stops the recording for the call +call.stopRecording(); +``` + + + + +```py +// starts recording +call.start_recording() + +// stops the recording for the call +call.stop_recording() +``` + + + + +```bash +curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}/start_recording?api_key=${API_KEY}" \ +-H "Authorization: ${JWT_TOKEN}" + +curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}/stop_recording?api_key=${API_KEY}" \ +-H "Authorization: ${JWT_TOKEN}" +``` + + + + +## List call recording + +This endpoint returns the list of recordings for a call. When using Stream S3 as storage (default) all links are signed and expire after 2-weeks. + + + + +```js +call.listRecordings(); +``` + + + + +```py +call.list_recordings(); +``` + + + + +```bash +curl -X GET "https://video.stream-io-api.com/video/call/default/${CALL_ID}/recordings?api_key=${API_KEY}" \ +-H "Authorization: ${JWT_TOKEN}" +``` + + + + +## Events + +These events are sent to users connected to the call and your webhook/SQS: + +- `call.recording_started` when the call recording has started +- `call.recording_stopped` when the call recording has stopped +- `call.recording_ready` when the recording is available for download +- `call.recording_failed` when recording fails for any reason + +## User Permissions + +The following permissions are checked when users interact with the call recording API. + +- `StartRecording` required to start the recording +- `StopRecording` required to stop the recording +- `ListRecordings` required to retrieve the list of recordings +- `DeleteRecording` required to delete an existing recording (including its files if stored using Stream S3 storage) + ## Enabling / Disabling call recording +Recording can be configured from the Dashboard (see call type screen) or directly via the API. It is also possible to change the recording settings for a call and override the default settings coming from the its call type. + @@ -52,86 +146,185 @@ call.update({ }); ``` + + + +```py +# Disable on call level +call.update_call(UpdateCallRequest( + settings_override=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='disabled', + ), + ), +)) + +# Disable on call type level +call_type_name = 'default' +call.update_call_type(call_type_name, UpdateCallTypeRequest( + settings=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='disabled', + ), + ), +)) + +# Automatically record calls +call.update_call_type(UpdateCallTypeRequest( + settings=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='auto', + ), + ), +)) + +# Enable +call.update_call(UpdateCallRequest( + settings_override=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='available', + ), + ), +)) + + +# Other settings +call.update_call(UpdateCallRequest( + settings_override=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='available', + quality='1080p', + ), + ), +)) +``` + -## Start call recording +## Audio only recording + +You can configure your calls to only record the audio tracks and exclude the video. You can do this from the dashboard (Call Types sections) or set it for individual calls. ```js -call.startRecording(); +// Enable +call.update({ + settings_override: { + recording: { + mode: VideoRecordSettingsRequestModeEnum.AVAILABLE, + audio_only: true, + }, + }, +}); ``` ```py -def hello_world(): - print("Hello, world!") +# Enable recording +call.update_call(UpdateCallRequest( + settings_override=CallSettingsRequest( + recording=RecordSettingsRequest( + mode='available', + audio_only=True + ), + ), +)) ``` - + -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") +## Recording layouts - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } +Recording can be customized in several ways: - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) +- You can pick one of the built-in layouts and pass some options to it +- You can customize more in detail the style of the call by providing your own CSS file +- You can use your own recording application - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } +There are three available layouts you can use for your calls: `"single_participant"`, `"grid"` and `"spotlight"` - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - startResponse, err := client.StartRecording(models.CallTypeDefault, callID, &models.StartRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } - fmt.Println("response", startResponse) +### Single Participant - stopResponse, err := client.StopRecording(models.CallTypeDefault, callID, &models.StopRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } +This layout shows only one participant video at the time, other video tracks are hidden. - fmt.Println("response", stopResponse) -} -``` + - - +The visible video is selected based on this priority: -```bash -curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}/start_recording?api_key=${API_KEY}" \ --H "Authorization: ${JWT_TOKEN}" -``` +- Participant is pinned +- Participant is screen-sharing +- Participant is the dominant speaker +- Participant has a video track - - +### Grid + +This layout shows a configurable number of tracks in a equally sized grid. + + + +### Spotlight + +This layout shows a video in a spotlight and the rest of the participants in a separate list or grid. + + -## Stop call recording +## Layout options + +Each layout has a number of options that you can configure, here is an example: + + ```js -call.stopRecording(); +const layoutOptions = { + 'logo.image_url': + 'https://theme.zdassets.com/theme_assets/9442057/efc3820e436f9150bc8cf34267fff4df052a1f9c.png', + 'logo.horizontal_position': 'center', + 'title.text': 'Building Stream Video Q&A', + 'title.horizontal_position': 'center', + 'title.color': 'black', + 'participant_label.border_radius': '0px', + 'participant.border_radius': '0px', + 'layout.spotlight.participants_bar_position': 'top', + 'layout.background_color': '#f2f2f2', + 'participant.placeholder_background_color': '#1f1f1f', + 'layout.single-participant.padding_inline': '20%', + 'participant_label.background_color': 'transparent', +}; + +client.video.updateCallType(callTypeName, { + settings: { + recording: { + mode: VideoRecordSettingsRequestModeEnum.AVAILABLE, + audio_only: false, + quality: VideoRecordSettingsRequestQualityEnum._1080P, + layout: { + name: VideoLayoutSettingsNameEnum.SPOTLIGHT, + options: layoutOptions, + }, + }, + }, +}); ``` @@ -143,65 +336,159 @@ def hello_world(): ``` - - -```go -func main() { - apiKey := os.Getenv("API_KEY") - apiSecret := os.Getenv("API_SECRET") - userID := os.Getenv("USER_ID") - url := os.Getenv("URL") - - if apiKey == "" || apiSecret == "" || userID == "" || url == "" { - panic("API_KEY, API_SECRET, USER_ID and URL env variables must be set") - } - - tokenProvider := videosdk.UserToken(apiSecret, userID, 24*time.Hour) - sdk := videosdk.New(apiKey, tokenProvider) - - token, err := tokenProvider(sdk.APIKey()) - if err != nil { - panic(err) - } + - client := videosdk.NewCoordinatorClientSDK(url, sdk.APIKey(), token) - startResponse, err := client.StartRecording(models.CallTypeDefault, callID, &models.StartRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } - fmt.Println("response", startResponse) +Here you can find the complete list of options available to each layout. + +### Single Participant + +| Option | Type | Default | Allowed Values | Description | +| ---------------------------------------- | ------- | ----------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| video.background_color | color | `#000000` | | The background color | +| video.screenshare_scale_mode | string | `fit` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| participant.label_horizontal_position | string | `left` | `[center left right]` | horizontal position for the participant label | +| participant.video_border_radius | number | `1.2` | | The corner radius used for the participant video border | +| logo.horizontal_position | string | `center` | `[center left right]` | horizontal position of the logo | +| participant.label_display | boolean | `true` | | Show the participant label | +| participant.label_text_color | color | `#000000` | | Text color of the participant label | +| participant.label_background_color | color | `#00000000` | | Background color of the participant label | +| participant.label_border_radius | number | `1.2` | | The corner radius used for the label border | +| logo.vertical_position | string | `top` | `[top bottom center]` | vertical position of the logo | +| participant.label_display_border | boolean | `true` | | Render label border | +| participant.label_vertical_position | string | `bottom` | `[top bottom center]` | vertical position for the participant label | +| participant.video_highlight_border_color | color | `#7CFC00` | | The color used for highlighted participants video border | +| participant.video_border_rounded | boolean | `true` | | Render the participant video border rounded | +| participant.video_border_width | boolean | `true` | | The stroke width used to render a participant border | +| participant.placeholder_background_color | color | `#000000` | | Sets the background color for video placeholder tile | +| video.scale_mode | string | `fill` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| logo.image_url | string | | | add a logo image to the video layout | +| participant.label_border_color | color | `#CCCCCC` | | Label border color | +| participant.label_border_rounded | boolean | `true` | | Render the label border rounded | +| participant.video_border_color | color | `#CCCCCC` | | The color used for the participant video border | + +### Spotlight + +| Option | Type | Default | Allowed Values | Description | +| ---------------------------------------- | ------- | ----------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| participant.video_border_width | boolean | `true` | | The stroke width used to render a participant border | +| grid.position | string | `bottom` | `[top bottom left right]` | position of the grid in relation to the spotlight | +| participant.label_display_border | boolean | `true` | | Render label border | +| participant.label_horizontal_position | string | `left` | `[center left right]` | horizontal position for the participant label | +| participant.video_border_color | color | `#CCCCCC` | | The color used for the participant video border | +| grid.columns | number | `5` | | how many column to use in grid mode | +| video.background_color | color | `#000000` | | The background color | +| logo.horizontal_position | string | `center` | `[center left right]` | horizontal position of the logo | +| participant.label_border_color | color | `#CCCCCC` | | Label border color | +| participant.label_background_color | color | `#00000000` | | Background color of the participant label | +| grid.cell_padding | size | `10` | | padding between cells | +| screenshare_layout | string | `spotlight` | `[grid spotlight single-participant]` | The layout to use when entering screenshare mode | +| grid.size_percentage | number | `20` | | The percentage of the screen the grid should take up | +| participant.label_border_radius | number | `1.2` | | The corner radius used for the label border | +| participant.video_highlight_border_color | color | `#7CFC00` | | The color used for highlighted participants video border | +| participant.placeholder_background_color | color | `#000000` | | Sets the background color for video placeholder tile | +| participant.video_border_radius | number | `1.2` | | The corner radius used for the participant video border | +| participant.label_display | boolean | `true` | | Show the participant label | +| participant.label_border_rounded | boolean | `true` | | Render the label border rounded | +| participant.video_border_rounded | boolean | `true` | | Render the participant video border rounded | +| grid.rows | number | `1` | | how many rows to use in grid mode | +| grid.margin | size | `10` | | the margin between grid and spotlight | +| video.scale_mode | string | `fill` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| logo.image_url | string | | | add a logo image to the video layout | +| logo.vertical_position | string | `top` | `[top bottom center]` | vertical position of the logo | +| video.screenshare_scale_mode | string | `fit` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| participant.label_text_color | color | `#000000` | | Text color of the participant label | +| participant.label_vertical_position | string | `bottom` | `[top bottom center]` | vertical position for the participant label | + +### Grid + +| Option | Type | Default | Allowed Values | Description | +| ---------------------------------------- | ------- | ----------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| logo.image_url | string | `` | | add a logo image to the video layout | +| logo.vertical_position | string | `top` | `[top bottom center]` | vertical position of the logo | +| participant.label_horizontal_position | string | `left` | `[center left right]` | horizontal position for the participant label | +| participant.placeholder_background_color | color | `#000000` | | Sets the background color for video placeholder tile | +| video.scale_mode | string | `fill` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| logo.horizontal_position | string | `center` | `[center left right]` | horizontal position of the logo | +| participant.video_border_rounded | boolean | `true` | | Render the participant video border rounded | +| participant.label_display_border | boolean | `true` | | Render label border | +| participant.label_border_color | color | `#CCCCCC` | | Label border color | +| grid.cell_padding | size | `10` | | padding between cells | +| video.screenshare_scale_mode | string | `fit` | `[fit fill]` | How source video is displayed inside a box when aspect ratio does not match. 'fill' crops the video to fill the entire box, 'fit' ensures the video fits inside the box by padding necessary padding | +| video.background_color | color | `#000000` | | The background color | +| participant.label_border_radius | number | `1.2` | | The corner radius used for the label border | +| grid.size_percentage | number | `90` | | The percentage of the screen the grid should take up | +| grid.margin | size | `10` | | the margin between grid and spotlight | +| grid.columns | number | `5` | | how many column to use in grid mode | +| participant.label_vertical_position | string | `bottom` | `[top bottom center]` | vertical position for the participant label | +| participant.label_display | boolean | `true` | | Show the participant label | +| participant.video_border_color | color | `#CCCCCC` | | The color used for the participant video border | +| participant.video_border_width | boolean | `true` | | The stroke width used to render a participant border | +| screenshare_layout | string | `spotlight` | `[grid spotlight single-participant]` | The layout to use when entering screenshare mode | +| participant.label_text_color | color | `#000000` | | Text color of the participant label | +| participant.label_background_color | color | `#00000000` | | Background color of the participant label | +| participant.label_border_rounded | boolean | `true` | | Render the label border rounded | +| participant.video_border_radius | number | `1.2` | | The corner radius used for the participant video border | +| participant.video_highlight_border_color | color | `#7CFC00` | | The color used for highlighted participants video border | +| grid.rows | number | `4` | | how many rows to use in grid mode | + +## Custom recording styling using external CSS + +You can customize how recorded calls look like by providing an external CSS file, the CSS file needs to be publicly available and ideally hosted on a CDN to ensure best performance. +The best way to find the right CSS setup is by running the layout app directly, the application is [publicly available on Github here](https://github.com/GetStream/stream-video-js/tree/main/sample-apps/react/egress-composite) and contains instructions on how to be used. - stopResponse, err := client.StopRecording(models.CallTypeDefault, callID, &models.StopRecordingRequest{ - Type: "default", ID: callID, - }) - if err != nil { - panic(err) - } + + - fmt.Println("response", stopResponse) -} +```js +client.video.updateCallType(callTypeName, { + settings: { + recording: { + mode: VideoRecordSettingsRequestModeEnum.AVAILABLE, + audio_only: false, + quality: VideoRecordSettingsRequestQualityEnum._1080P, + layout: { + name: VideoLayoutSettingsNameEnum.SPOTLIGHT, + external_css_url: 'https://path/to/custom.css', + }, + }, + }, +}); ``` - + -```bash -curl -X POST "https://video.stream-io-api.com/video/call/default/${CALL_ID}/start_recording?api_key=${API_KEY}" \ --H "Authorization: ${JWT_TOKEN}" +```py +def hello_world(): + print("Hello, world!") ``` -## List call recording +## Advanced - record calls using a custom web application + +If needed, you can use your own custom application to record a call. This is the most flexible and complex approach to record calls, make sure to reach out to our customer support before going with this approach. + +The layout app used to record calls is available on Github and is a good starting point, the repository also includes information on how to build your own. ```js -call.listRecordings(); +client.video.updateCallType(callTypeName, { + settings: { + recording: { + mode: VideoRecordSettingsRequestModeEnum.AVAILABLE, + audio_only: false, + quality: VideoRecordSettingsRequestQualityEnum._1080P, + layout: { + name: VideoLayoutSettingsNameEnum.CUSTOM, + external_app_url: 'https://path/to/layout/app', + }, + }, + }, +}); ``` @@ -212,24 +499,9 @@ def hello_world(): print("Hello, world!") ``` - - - -```go -TODO -``` - - - - -```bash -curl -X GET "https://video.stream-io-api.com/video/call/default/${CALL_ID}/${SESSION_ID}/recordings?api_key=${API_KEY}" \ --H "Authorization: ${JWT_TOKEN}" -``` - -## Delete call recording +## Client-side recording -// TODO: Add API endpoint +Unfortunately there is no direct support for client-side recording at the moment. Call recording at the moment is done by Stream server-side. If client-side recording is important for you please make sure to follow the conversation [here](https://github.com/GetStream/protocol/discussions/249). diff --git a/docusaurus/video/docusaurus/docs/api/streaming/backstage.mdx b/docusaurus/video/docusaurus/docs/api/streaming/backstage.mdx index 67fe5124..d7a56b77 100644 --- a/docusaurus/video/docusaurus/docs/api/streaming/backstage.mdx +++ b/docusaurus/video/docusaurus/docs/api/streaming/backstage.mdx @@ -7,6 +7,7 @@ title: Backstage import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import GoLive from '../_common_/go_live.mdx' ## Introduction @@ -35,6 +36,30 @@ client.video.updateCallType('', { }); ``` + + + +```py +# call level update +call.update_call( + settings_override=CallSettingsRequest( + backstage=BackstageSettingsRequest( + enabled=True, + ), + ), +) + +# call type level update +call_type_name = '' +call.update_call_type( + settings=CallSettingsRequest( + backstage=BackstageSettingsRequest( + enabled=True, + ), + ), +) +``` + @@ -52,20 +77,22 @@ client.video.updateCallType('', { ``` - - -## Go Live - - - + -```js -call.goLive(); +```py +callTypeName = '' +call.update_call_type( + grants={"host": [OwnCapability.JOIN_BACKSTAGE.to_str()]}, +) ``` +## Go Live + + + ## Stop Live @@ -75,5 +102,12 @@ call.goLive(); call.stopLive(); ``` + + + +```py +call.stop_live() +``` + diff --git a/docusaurus/video/docusaurus/docs/api/streaming/overview.mdx b/docusaurus/video/docusaurus/docs/api/streaming/overview.mdx index a198786a..8b9907f8 100644 --- a/docusaurus/video/docusaurus/docs/api/streaming/overview.mdx +++ b/docusaurus/video/docusaurus/docs/api/streaming/overview.mdx @@ -4,3 +4,95 @@ sidebar_position: 1 slug: /streaming/overview title: Overview --- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import GoLive from '../_common_/go_live.mdx'; +import RTMP from '../_common_/rtmp.mdx'; + +In this section we are going to explain how you can use Stream to power different livestream use-cases. + +Stream video allows you to power ultra low-latency streaming (hundreds of milliseconds). This is made possible by our worldwide edge infrastructure which support WebRTC for consuming and sending video. + +Other important features related to livestream that are discussed in this section: + +- Multiple streams & co-hosts +- RTMP in and WebRTC input +- Exporting to HLS +- Recording + +## Quickstart + +Before diving into the detail, let's get a livestream up and running. + +### Create a new livestream call using the API + + + + +```javascript +call = client.video.call('livestream', callId); +const response = await call.getOrCreate({ + data: { + created_by_id: 'john', + members: [{ user_id: 'john', role: 'host' }], + }, +}); +``` + + + + +```py +def hello_word(): + print("Hello world") +``` + + + + +By default livestreams are created in backstage mode, when in backstage mode streams can only be accessed by admin-like users. +This is necessary because it makes it possible to create the setup in advance and to notify and grant access to viewers when the event starts. + +All we need to do in this case is call the `GoLive` method on the call object and that will make it accessible to viewers. + + + +### Create a simple test app using the livestream video player + +TODO: point to a simple webapp where you can run our video player, OK to use something like Codesanbox + +### Test sending video using WebRTC + +You can refer to this [CodeSandbox](https://codesandbox.io/s/livestream-tutorial-mlqtrd?file=/src/App.tsx) - where you can send videos via WebRTC + +### Test sending video via RTMP using OBS + +Let's keep the demo app open and try to send video to the same call using RTMP. + +#### Log the URL & Stream Key + +​ + + + +#### Open OBS and go to settings -> stream + +​ +Select "custom" service +Server: equal to the rtmpURL from the log +Stream key: equal to the streamKey from the log + +Press start streaming in OBS. The RTMP stream will now show up in your call just like a regular video participant. + +Now that we've learned to publish using WebRTC or RTMP let's talk about watching the livestream. + +### Set the call live + +Until now we connected to the call as the host user. By default livestream calls are configured to only allow hosts to send audio/video tracks. +Viewers on the other hand are not + +``` +// TODO: ... +``` diff --git a/docusaurus/video/docusaurus/docs/api/streaming/rtmp.mdx b/docusaurus/video/docusaurus/docs/api/streaming/rtmp.mdx index c4498094..d5d0ce7f 100644 --- a/docusaurus/video/docusaurus/docs/api/streaming/rtmp.mdx +++ b/docusaurus/video/docusaurus/docs/api/streaming/rtmp.mdx @@ -31,19 +31,20 @@ const address = resp.call.ingress.rtmp.address.replace( ```py -def hello_world(): - print("Hello, world!") -``` - - - -```go -callResponse, err := client.GetOrCreateCall(models.CallTypeDefault, callID, &models.GetOrCreateCallRequest{}) -if err != nil { - panic(err) -} -addressToUse := callResponse.Call.Ingress.Rtmp.Address +# create or get the call +response = call.get_or_create_call( + data=CallRequest( + created_by_id='john' + ) +) + +# user ID of an existing user +userId = 'jane' +token = client.create_token(userId) + +# replace in rtmp address with actual token +address = response.data().call.ingress.rtmp.address.replace('', token) ``` diff --git a/docusaurus/video/docusaurus/docs/api/webhooks/events.mdx b/docusaurus/video/docusaurus/docs/api/webhooks/events.mdx index be106fc8..2567b21d 100644 --- a/docusaurus/video/docusaurus/docs/api/webhooks/events.mdx +++ b/docusaurus/video/docusaurus/docs/api/webhooks/events.mdx @@ -7,30 +7,35 @@ title: Events Here you can find the list of events are sent to Webhook and SQS. -| Event Type | Description | -|---------------------------------|------------------------------------------------| -| call.accepted | Sent when a user accepts an incoming call | -| call.blocked_user | Sent when a user is blocked | -| call.broadcasting_started | Sent when HLS broadcasting has started | -| call.broadcasting_stopped | Sent when HLS broadcasting is stopped | -| call.created | Sent when a call is created | -| call.ended | Sent when a call is marked as ended | -| call.live_started | Sent when a call goes live | -| call.member_added | Sent when a member is added | -| call.member_removed | Sent when a member is removed | -| call.member_updated | Sent when a member is updated | -| call.member_updated_permission | Sent when permissions are updated for a member | -| call.permission_request | Sent when a user requests access on a call | -| call.permissions_updated | Sent when permissions are updated on a call | -| call.reaction_new | Sent when a reaction is sent on a call | -| call.recording_started | Sent when call recording starts | -| call.recording_stopped | Sent when call recording is stopped | -| call.rejected | Sent when a user rejects an incoming call | -| call.session_ended | Sent when the session has ended | -| call.session_participant_joined | Sent when a user joins a call | -| call.session_participant_left | Sent when a user leaves a call | -| call.session_started | Sent when a call session starts | -| call.unblocked_user | Sent when a user is unblocked | -| call.updated | Sent when a call is updated | +| Event Type | Description | +|---------------------------------|---------------------------------------------------| +| call.accepted | Sent when a user accepts an incoming call | +| call.blocked_user | Sent when a user is blocked | +| call.broadcasting_started | Sent when HLS broadcasting has started | +| call.broadcasting_stopped | Sent when HLS broadcasting is stopped | +| call.created | Sent when a call is created | +| call.ended | Sent when a call is marked as ended | +| call.live_started | Sent when a call goes live | +| call.member_added | Sent when a member is added | +| call.member_removed | Sent when a member is removed | +| call.member_updated | Sent when a member is updated | +| call.member_updated_permission | Sent when permissions are updated for a member | +| call.permission_request | Sent when a user requests access on a call | +| call.permissions_updated | Sent when permissions are updated on a call | +| call.reaction_new | Sent when a reaction is sent on a call | +| call.recording_started | Sent when call recording starts | +| call.recording_stopped | Sent when call recording is stopped | +| call.rejected | Sent when a user rejects an incoming call | +| call.session_ended | Sent when the session has ended | +| call.session_participant_joined | Sent when a user joins a call | +| call.session_participant_left | Sent when a user leaves a call | +| call.session_started | Sent when a call session starts | +| call.unblocked_user | Sent when a user is unblocked | +| call.updated | Sent when a call is updated | +| call.recording_started | Sent when call recording has started | +| call.recording_stopped | Sent when call recording has stopped | +| call.recording_ready | Sent when the recording is available for download | +| call.recording_failed | Sent when recording fails for any reason | + You can find the definition of each events in the OpenAPI spec available [here](https://github.com/GetStream/protocol/blob/main/openapi/video-openapi.yaml)