Giter Club home page Giter Club logo

zegocloud_server_demos's Introduction

This article is for teaching purposes only, and ZEGOCLOUD does not provide any maintenance and technical support for it.

How To Run This Demo

This is a Next.js project bootstrapped with create-next-app.

Setting Environment Variables

Before running this demo, you need to set the following environment variables in .env.local file:

ZEGOCLOUD_APP_ID=
ZEGOCLOUD_SERVER_SECRET=
ZEGOCLOUD_APP_SIGN=
REDIS_HOST=
REDIS_PORT=
REDIS_USER=
REDIS_PSWD=

Getting Started

First, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev

Open http://localhost:3000 with your browser to see the result.

You can start editing the page by modifying pages/index.js. The page auto-updates as you edit the file.

API routes can be accessed on http://localhost:3000/api/hello. This endpoint can be edited in pages/api/hello.js.

The pages/api directory is mapped to /api/*. Files in this directory are treated as API routes instead of React pages.

This project uses next/font to automatically optimize and load Inter, a custom Google Font.

Learn More

To learn more about Next.js, take a look at the following resources:

You can check out the Next.js GitHub repository - your feedback and contributions are welcome!

Deploy on Vercel

The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.

Check out our Next.js deployment documentation for more details.

Implement room list service


During application development, it is often necessary for you to implement a room business service to provide a better experience for app users in most interactive scenarios such as live streaming, voice chat rooms, meetings, online classrooms, etc.

  • You need to show an entrance list for app users, allowing them to select the units they are interested in and join the room for interaction.
  • After app users join the room, you need to show them the detailed information of the room and provide special features for different roles. For example, the host can start live streaming and the audience can send virtual gifts.

Considering the different business scopes and requirements, we set that you need to maintain your own room/channel data on the server-side and implement features such as data pulling, display, and timely refreshing on the client-side.

This doc will introduce how to implement a room list service.

This demo only implements the main logic of a simple room list, and you need to expand other business logic by yourself.

Prerequisites

  • You need to have your own business server in order to provide API interfaces for clients.
  • (Recommended) You need to have your own database service to persistently store necessary data.
  • Downlaod the Demo source code

Preview the effect

This doc will guide you through implementing the following APIs related to the room list service:

  • /room/create_room: used for the host to create a live stream room.
  • /room/get_room_list: used for clients to fetch the list of available rooms.
  • /room/get_room_user_list: used to fetch the list of members in a room.
  • /room/end_room: used for the host to end the live stream room.
  • /room/join_room: used for an audience to join a live stream room and update the member list.
  • /room/leave_room: used for an audience to leave a live stream room and update the member list.
  • /room/heartbeat: a client heartbeat used for the server to check the user's connection status.

Understand the tech

  1. The concept of "room" mentioned in this article refers to the rooms on your business server, rather than those on ZEGOCLOUD. The rooms on ZEGOCLOUD do not need to be created - they are automatically created when a client user joins the room, and are automatically destroyed when all users leave. Therefore, you only need to manage the lifecycle of the rooms on your business server, and do not need to worry about the rooms on ZEGOCLOUD.
  2. You can simply associate the rooms on your business server with those on ZEGOCLOUD using the RoomID.

In order to give you a general understanding of the workflow, the following diagram describes the business process of the "host creating a live stream, audience joining the live stream, host ending the live stream, audience leaving the live stream" in a live streaming scenario. Of course, this process also applies to other scenarios, such as a conference list, game room list, etc.

sequenceDiagram
    participant Host
    participant Server
    participant Audience

    Host->>Server: Create a live stream room  /room/create_room
    Server-->>Host: Successfully created
    Host->>Server: Join a live stream room /room/join_room
    Server-->>Host: Successfully joined
    loop Continuous heartbeat
        Host->>Server: Send heartbeat /room/heartbeat
        Server-->>Host: Heartbeat response
    end


    Audience->>Server: Get the list of live stream rooms /room/get_room_list
    Server-->>Audience: Return the list of live stream rooms


    Audience->>Server: Join a live stream room /room/join_room
    Server-->>Audience: Successfully joined


    Audience->>Server: Get the list of members in the room /room/get_room_user_list
    Server-->>Audience: Return the list of members in the room

    loop Continuously sending heartbeats
        Audience->>Server: Send heartbeat /room/heartbeat
        Server-->>Audience: Heartbeat response
    end


    Host->>Server: End the live stream room  /room/end_room
    Server-->>Host: Successfully closed


    Audience->>Server: Leave the live stream room  /room/leave_room
    Server-->>Audience: Successfully left
Loading

The common room list service requires the following information to be processed:

  • Room list and room info: room ID, room name, special role IDs in the room (e.g. host, teacher, etc.), and other room business info (such as room type, etc.).
  • Room user list and user information: user ID, user name, entry time, and heartbeat time (for a detailed introduction of user heartbeat, please refer to the following text), as well as other business logic fields and other user info (such as user level, etc.).
The data structure of the room list service is illustrated by the following examples:

You can further optimize and modify it according to specific scenario requirements and the type of database used.

{
  "room_id_1": {
    "room_id": "room_id_1",
    "host_id": "user_id_tom",
    "room_name": "live_room_abc",
    "user_list": [
      {
        "user_id": "user_id_tom",
        "user_name": "tom",
        "join_time": 1631846000000,
        "heartbeat_time": 1631846010000
      },
      {
        "user_id": "user_id_789",
        "user_name": "jerry2",
        "join_time": 1631848000000,
        "heartbeat_time": 1631848010000
      }
    ]
  },
  "room_id_2": {
    "room_id": "room_id_2",
    "host_id": "user_id_jerry",
    "room_name": "live_room_def",
    "user_list": [
      {
        "user_id": "user_id_jerry",
        "user_name": "tom1",
        "join_time": 1631849000000,
        "heartbeat_time": 1631849010000
      },
      {
        "user_id": "user_id_890",
        "user_name": "jerry2",
        "join_time": 1631851000000,
        "heartbeat_time": 1631851010000
      }
    ]
  }
}

Implementation

For better understanding, this article will provide two types of sample code:

  1. One way is to let the server directly use memory to manage data. This method can help you better understand the server's business logic. However, please note that the data in memory will be lost after the server restarts, so it is not recommended to use this method.
  2. Another way is to take Redis as an example and explain how to use a cache database or other databases (such as SQLServer, Firebase-DataBase, etc.) to implement a standard room list service.

The sample code in this article uses live streaming scenarios as an example but is also applicable to other scenarios, such as conference lists, game room lists, etc.

Creating and Ending Rooms

In a room list service, the lifecycle of a room object begins with creating a room and ends with ending a room.

1. Use memory or database services to manage data
  • Using Memory (Not recommended)
// Declare variables directly in memory to manage data
const _roomList = {};
  • Using Redis
// Use a cache database like Redis or other databases (such as SQLServer, Firebase-DataBase, etc.) to manage data
const client = new Redis({
  host: REDIS_HOST,
  port: REDIS_PORT ,
  username: REDIS_USER,
  password: REDIS_PSWD
});

// And bind necessary interfaces such as set, get, delete, etc.
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
const delAsync = promisify(client.del).bind(client);
2. When the client calls the create room API, the server performs necessary error checks and updates the data - creating and saving a room object, recording the room ID, room name, room creator, and other information.
  • Using Memory (Not recommended)
async function _createRoom(RoomID, RoomName, HostID) {
  try {
    if (_roomList[RoomID]) {
      return {
        "code": 1,
        "message": "room already exists",
      };
    } else {
      _roomList[RoomID] = {
        "room_id": RoomID,
        "room_name": RoomName,
        "host_id": HostID,
        "users": {},
      };
      return {
        "code": 0,
        "message": "success",
      };
    }
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "create room failed",
    };
  }
}
  • Using Redis
async function _createRoom(RoomID, RoomName, HostID) {
  try {
    const existingRoom = await getAsync(RoomID);
    if (existingRoom) {
      return {
        "code": 1,
        "message": "room already exists",
      };
    } else {
      const room = {
        "room_id": RoomID,
        "room_name": RoomName,
        "host_id": HostID,
        "users": {},
      };
      await setAsync(RoomID, JSON.stringify(room));
      return {
        "code": 0,
        "message": "success",
      };
    }
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "create room failed",
    };
  }
}
3. When the client calls the end room API, necessary error checks are performed and data is updated - the corresponding room object is found and deleted.
  • Using Memory (Not recommended)
async function _endRoom(RoomID) {
  try {
    if (_roomList[RoomID]) {
      delete _roomList[RoomID];
      return {
        "code": 0,
        "message": "success",
      };
    } else {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "end room failed",
    };
  }
}
  • Using Redis
async function _endRoom(RoomID) {
  try {
    const existingRoom = await getAsync(RoomID);
    if (existingRoom) {
      await delAsync(RoomID);
      return {
        "code": 0,
        "message": "success",
      };
    } else {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "end room failed",
    };
  }
}

Getting the Room List

When the client requests to get the list of rooms, the server should query the list of rooms from the database and return necessary info to the client so that the client user can select and join a particular room.

Notes:

  1. Usually, you need to selectively return necessary information instead of returning all the server data to the client, as shown in the reference code.
  2. If there are many rooms, you need to consider adding the feature of pagination for this interface.
  3. You can return a reordered room list to the client based on factors such as room popularity or user preference.
The reference code for the demo is as follows:

In the reference code, to reduce the length of the response body, only necessary info such as room ID, room name, and host name is returned. You can add other necessary info as needed, such as the URL of the room cover image and the room description.

  • Using Memory (Not recommended)
async function _getRoomList() {
  try {
    const roomList = Object.values(_roomList).map((room) => {
      return {
        room_id: room.room_id,
        room_name: room.room_name,
        host_id: room.host_id,
      };
    });
    return {
      "code": 0,
      "message": "success",
      "data": roomList,
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "get room list failed",
    };
  }
}
  • Using Redis
async function _getRoomList() {
  try {
    const keys = await promisify(client.keys).bind(client)("*");
    const roomList = await Promise.all(
      keys.map(async (key) => {
        const roomStr = await getAsync(key);
        const room = JSON.parse(roomStr);
        return {
          room_id: room.room_id,
          room_name: room.room_name,
          host_id: room.host_id,
        };
      })
    );
    return {
      "code": 0,
      "message": "success",
      "data": roomList,
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "get room list failed",
    };
  }
}

Joining and Leaving Rooms

The lifecycle of the room user object in the server data usually starts with joining the room and ends with leaving the room - the logic of joining and leaving the room is similar to Creating and Ending Rooms, which involves adding and deleting server data.

If there are special roles in your business, usually the info of these special roles will be bound to the room data. (For example, in the sample demo in this article, the information of the host role is stored in the host_id field in the room data structure.)

1. When a user joins a room, first retrieve the room object from the database, and then add the user info to the member list of the room.
  • Using Memory (Not recommended)
async function _joinRoom(RoomID, UserID, UserName) {
  try {
    if (!_roomList[RoomID]) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = _roomList[RoomID];
    if (room.users && room.users[UserID]) {
      return {
        "code": 1,
        "message": "user already joined",
      };
    }

    if (!room.users) {
      room.users = {};
    }

    const timestamp = new Date().getTime();
    room.users[UserID] = {
      "user_id": UserID,
      "user_name": UserName,
      "join_time": timestamp,
      "heartbeat_time": timestamp,
    };

    return {
      "code": 0,
      "message": "success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "join room failed",
    };
  }
}
  • Using Redis
async function _joinRoom(RoomID, UserID, UserName) {
  try {
    const roomString = await getAsync(RoomID);

    if (!roomString) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = JSON.parse(roomString);

    if (room.users && room.users[UserID]) {
      return {
        "code": 1,
        "message": "user already joined",
      };
    }

    if (!room.users) {
      room.users = {};
    }

    const timestamp = new Date().getTime();
    room.users[UserID] = {
      "user_id": UserID,
      "user_name": UserName,
      "join_time": timestamp,
      "heartbeat_time": timestamp,
    };

    await setAsync(RoomID, JSON.stringify(room));

    return {
      "code": 0,
      "message": "join room success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "join room failed",
    };
  }
}
2. When a user leaves a room, delete the user from the member list of the room.
  • Using Memory (Not recommended)
async function _leaveRoom(RoomID, UserID) {
  try {
    if (!_roomList[RoomID]) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = _roomList[RoomID];
    if (!room.users || !room.users[UserID]) {
      return {
        "code": 1,
        "message": "user not exists in room",
      };
    }

    delete room.users[UserID];

    return {
      "code": 0,
      "message": "success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "leave room failed",
    };
  }
}
  • Using Redis
async function _leaveRoom(RoomID, UserID) {
  try {
    const roomString = await getAsync(RoomID);

    if (!roomString) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = JSON.parse(roomString);

    if (!room.users || !room.users[UserID]) {
      return {
        "code": 1,
        "message": "user not in room",
      };
    }

    delete room.users[UserID];

    await setAsync(RoomID, JSON.stringify(room));

    return {
      "code": 0,
      "message": "leave room success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "leave room failed",
    };
  }
}

Getting the Room Member List

Similar to the Getting the Room List interface, after a user joins a room, it is usually necessary to request the server to obtain the room member list, and the server should query the user list from the database or cache and return necessary information to the client.

Notes:

  1. If there is a lot of data on the server side, you need to selectively return necessary info and consider adding pagination retrieval function to this interface.
  2. Based on business needs, you can add other attributes to the members of the room, such as user permissions.
The reference code for the demo is as follows:
  • Using Memory (Not recommended)
async function _getRoomUserList(RoomID) {
  try {
    if (!_roomList[RoomID]) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = _roomList[RoomID];
    const userList = room.users ? Object.values(room.users) : [];

    return {
      "code": 0,
      "message": "success",
      "data": userList,
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "get user list failed",
    };
  }
}
  • Using Redis
async function _getRoomUserList(RoomID) {
  try {
    const roomString = await getAsync(RoomID);

    if (!roomString) {
      return {
        "code": 1,
        "message": "room not exists",
        userList: [],
      };
    }

    const room = JSON.parse(roomString);
    const userList = Object.values(room.users || {});

    return {
      "code": 0,
      "message": "get user list success",
      userList,
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "get user list failed",
      userList: [],
    };
  }
}

User Heartbeat

User heartbeat is a mechanism that refers to the client regularly sending requests to the server. Through this process, the server can determine whether the client is online and whether the connection between the client and the server is normal. If the server does not receive a heartbeat request from the client within a certain period of time, it can be considered that the client is offline, and then take corresponding measures, such as clearing the client's status info, releasing resources, etc., to improve the stability and reliability of the app.

In our sample demo, the user list in the room will be further maintained based on user heartbeat.

1. The client needs to regularly send a heartbeat to the server to notify the server of its online status.

At the same time, the server will inform the client whether it is still in a normal connection. If the client has disconnected abnormally, the client usually needs to handle the business logic such as relogin.

  • Using Memory (Not recommended)
async function _heartbeat(RoomID, UserID) {
  try {
    if (!_roomList[RoomID]) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = _roomList[RoomID];

    if (!room.users || !room.users[UserID]) {
      return {
        "code": 1,
        "message": "user not joined or heartbeat timeout",
      };
    }

    const now = new Date().getTime();
    const userhb = room.users[UserID].heartbeat_time;
    if (now - userhb > HEARTBEAT_TIMEOUT) {
      return {
        "code": 1,
        "message": "heartbeat timeout",
      };
    }

    room.users[UserID].heartbeat_time = now;

    return {
      "code": 0,
      "message": "heartbeat success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": "heartbeat failed",
    };
  }
}
  • Using Redis
async function _heartbeat(RoomID, UserID) {
  try {
    const roomString = await getAsync(RoomID);

    if (!roomString) {
      return {
        "code": 1,
        "message": "room not exists",
      };
    }

    const room = JSON.parse(roomString);


    if (!room.users || !room.users[UserID]) {
      return {
        "code": 1,
        "message": "user not joined or heartbeat timeout",
      };
    }

    const now = new Date().getTime();
    const userhb = room.users[UserID].heartbeat_time;
    if (now - userhb > HEARTBEAT_TIMEOUT) {
      return {
        "code": 1,
        "message": "heartbeat timeout",
      };
    }

    room.users[UserID].heartbeat_time = now;

    await setAsync(RoomID, JSON.stringify(room));

    return {
      "code": 0,
      "message": "heartbeat success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": `heartbeat failed,error:${err}`,
    };
  }
}
2. The server will regularly check the heartbeat data in the room, consider users with heartbeat timeout as offline, and remove them from the room list. (This often happens when a user kills the app directly, or the user's client has an abnormal network disconnection, etc.)

When the removed client initiates a heartbeat request again, the server will inform the client that a heartbeat timeout has occurred.

  • Using Memory (Not recommended)
setInterval(() => {
  _checkHeartbeatTime();
}, HEARTBEAT_TIMEOUT / 3 * 2);


async function _checkHeartbeatTime() {
  try {
    const keys = Object.keys(_roomList);
    await Promise.all(keys.map(async (key) => {
      const room = _roomList[key];
      const users = room.users;
      const expiredUserIDs = Object.keys(users).filter(function (userID) {
        const now = Date.now();
        const userhb = users[userID].heartbeat_time;
        console.log(`now: ${now}, userhb: ${userhb}, diff: ${now - userhb}`);
        return now - userhb > HEARTBEAT_TIMEOUT;
      });
      if (expiredUserIDs.length > 0) {
        expiredUserIDs.forEach((userID) => {
          console.log('mem: User ${userID} has been removed from room ${room.room_id}');
          delete users[userID];
        });
        _roomList[room.room_id] = room;
      } else {
        // console.log('mem: timeout userList is empty');
      }
    }));
    return {
      "code": 0,
      "message": "success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": err,
    };
  }
}
  • Using Redis
// Start user heartbeat check after connecting to Redis.
client.on('connect', () => {
  console.log('Connected to Redis');
  setInterval(() => {
    _checkHeartbeatTime();
  }, HEARTBEAT_TIMEOUT / 3 * 2);
});

async function _checkHeartbeatTime() {
  try {
    const keys = await promisify(client.keys).bind(client)("*");
    await Promise.all(keys.map(async (key) => {
      const room = JSON.parse(await promisify(client.get).bind(client)(key));
      const users = room.users;
      const expiredUserIDs = Object.keys(users).filter(function (userID) {
        const now = Date.now();
        const userhb = users[userID].heartbeat_time;
        return (now - userhb) > HEARTBEAT_TIMEOUT;
      });
      if (expiredUserIDs.length > 0) {
        expiredUserIDs.forEach((userID) => {
          console.log(`redis: User ${userID} has been removed from room ${room.room_id}`);
          delete users[userID];
        });
        await promisify(client.set).bind(client)(room.room_id, JSON.stringify(room));
      } else {
        // console.log(`redis: timeout userList is empty`);
      }
    }));
    return {
      "code": 0,
      "message": "success",
    };
  } catch (err) {
    console.error(err);
    return {
      "code": 1,
      "message": err,
    };
  }
}

User heartbeat is an indispensable mechanism in app development, which can help you better handle the connection between the client and the server, and provide a better user experience. In some real-time app scenarios, such as online games, interactive videos, etc., user heartbeat can also help the server update other status information of the client. You can expand the status information of user heartbeat according to your own business needs, such as user viewing time, user points, user geographic location, etc. Through these status info the server can better grasp the user's behavior and provide better services and experience.

Demo's Room List Server Protocol
  • Creating rooms

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/create_room?RoomID=room1&RoomName=room1&HostID=host
  • Using Redis
http://[your.server.domain]/api/room/mem/create_room?RoomID=room1&RoomName=room1&HostID=host

Response:

{
    "code": 0,
    "message": "success"
}
  • Ending rooms

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/end_room?RoomID=room1
  • Using Redis
http://[your.server.domain]/api/room/mem/end_room?RoomID=room1

Response:

{
    "code": 0,
    "message": "success"
}
  • Getting room list

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/get_room_list
  • Using Redis
http://[your.server.domain]/api/room/mem/get_room_list

Response:

{
    "code": 0,
    "message": "success",
    "data": [
        {
            "room_id": "room1",
            "room_name": "room1",
            "host_id": "host1"
        }
    ]
}
  • Joining rooms

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/join_room?RoomID=room1&UserID=user2&UserName=user2
  • Using Redis
http://[your.server.domain]/api/room/mem/join_room?RoomID=room1&UserID=user2&UserName=user2

Response:

{
    "code": 0,
    "message": "success",
}
  • Leaving rooms

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/leave_room?RoomID=room1&UserID=user2
  • Using Redis
http://[your.server.domain]/api/room/mem/leave_room?RoomID=room1&UserID=user2

Response:

{
    "code": 0,
    "message": "success",
}
  • Getting user list

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/get_room_user_list?RoomID=room1
  • Using Redis
http://[your.server.domain]/api/room/mem/get_room_user_list?RoomID=room1

Response:

{
	"code": 0,
	"message": "get user list success",
	"userList": [{
		"user_id": "user1",
		"user_name": "user1",
		"join_time": 1679412810925,
		"heartbeat_time": 1679412810925
	}, {
		"user_id": "user2",
		"user_name": "user2",
		"join_time": 1679412810925,
		"heartbeat_time": 1679412810925
	}]
}
  • User heartbeat

  • Using Memory (Not recommended)

http://[your.server.domain]/api/room/redis/heartbeat?RoomID=room1&UserID=user2
  • Using Redis
http://[your.server.domain]/api/room/mem/heartbeat?RoomID=room1&UserID=user2

Response:

{
    "code": 0,
    "message": "heartbeat success",
}

Summary

So far, you have completed a basic room list service.

  • You can extend more features based on this framework, such as

    • Add more attributes to the room, such as room preview picture, creation time, room mode, etc.
    • Add more attributes to the user, such as user permissions, user roles, user avatars, etc.
    • Make more flexible control over the room lifecycle - such as automatically creating a room in the background, not destroying a specific room, etc.
  • Additionally, you can further optimize the architecture, such as

    • Store the basic info of the room list in SQL Server to obtain more reliable persistent storage.
    • Store dynamic info in the room in other more efficient data structures, such as Redis cluster or Firebase Realtime Database, to achieve better real-time update and synchronization performance.
    • You can also add authentication function to the room list service API based on your user login system to identify requests from your real users and improve security.

ZEGOCLOUD provides you with a rich set of server APIs, such as Room signaling API, Streaming control API, etc., as well as various event callbacks. You can use them to further extend your room services. Please refer to Server APIs and Callback for more information.

You can read this doc to learn how to extend your room list server capabilities using the features provided by ZEGOCLOUD: [TODO]

If you have any suggestions or comments, feel free to share them with us via Discord|_blank. We value your feedback.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.