{"id":27999264,"url":"https://github.com/voximplant/solutions-videoconference","last_synced_at":"2025-05-08T22:57:32.557Z","repository":{"id":37543946,"uuid":"265244682","full_name":"voximplant/solutions-videoconference","owner":"voximplant","description":"Complete videoconferencing application example","archived":false,"fork":false,"pushed_at":"2023-02-04T10:40:24.000Z","size":87308,"stargazers_count":18,"open_issues_count":16,"forks_count":7,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-08T22:57:23.941Z","etag":null,"topics":["demo","tutorial","videochat","voximplant"],"latest_commit_sha":null,"homepage":"https://videoconf.voximplant.com/","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/voximplant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-19T12:44:29.000Z","updated_at":"2025-04-24T14:32:35.000Z","dependencies_parsed_at":"2023-02-18T15:46:02.636Z","dependency_job_id":null,"html_url":"https://github.com/voximplant/solutions-videoconference","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-videoconference","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-videoconference/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-videoconference/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-videoconference/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voximplant","download_url":"https://codeload.github.com/voximplant/solutions-videoconference/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253160790,"owners_count":21863627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["demo","tutorial","videochat","voximplant"],"created_at":"2025-05-08T22:57:31.812Z","updated_at":"2025-05-08T22:57:32.546Z","avatar_url":"https://github.com/voximplant.png","language":"Vue","readme":"# videoconf-frontend\n\n## Quickstart\n\n### Videoconf capabilities:\n\n- Work with the Voximpant platform\n- Create conferences\n- Join existing conferences\n- Choose audio and video devices during conferences\n- Disable microphone and camera during conferences\n- Screen sharing in several modes: screen+audio+camera+microphone, screen+camera+microphone, screen+microphone\n- User reactions\n- Create conference chats\n- Display conference participants\n- Request suitable video resolution from the server\n- Sign up and sign in to videoconf via google account\n- Customize video layouts and create your ones via [@voximplant/tiler](https://github.com/voximplant/tiler)\n- Customize conference participants' order\n- Notify users of devices errors\n\nSigning up and signing in are processed via a backend server.\n\n### Backend deployment and setup\n\nThe backend server provides signing up/signing in and other information for the client.  \n[videoconf-backend project and documentation](https://github.com/voximplant/solutions-videoconference/tree/master/server).\n\n#### .env file configuration\n\n- Fill in the service account to the SERVICE_ACCOUNT_CREDS field. Read more about service accounts [in this article](https://voximplant.com/docs/gettingstarted/basicconcepts/managementapi)\n- Fill in the application ID to the APPLICATION_ID field. You can find the ID in the [applications](https://manage.voximplant.com/applications) section under your application name\n\nTo set up `web oauth config`, go to the [google oauth credentials](https://console.cloud.google.com/apis/credentials) section, choose \"Create credentials\", and \"OAuth client ID\".\nAfter creating your credentials, you can view them via clicking the «Edit OAuth client» button.\n- Fill in your Client ID to the GOOGLE_AUTH_CLIENT_ID field\n- Fill in your Client's secret key to the GOOGLE_AUTH_SECRET field\n- Fill in your authorization URI to the GOOGLE_AUTH_REDIRECT_URI field. The redirect can be `{{frontend}}/auth-endpoint` or `http://localhost:8080/auth` for local developments\n\n#### Local deployment\n\nInstall modules and dependencies:\n\n```\nyarn \n```\n\nStart production build in pm2\n\n```\nyarn start:pm2\n```\n\n#### Docker deployment\n\n##### Build and push the docker image\n\n- run `make docker-build`\n- run `make docker-push`\n\n##### Run on a server\n\n- fill in the private keys into the `.private` folder\n- fill in the environment variables to `.env` (check `.env.example`)\n- log in to the docker registry `registry.zingaya.com/video/videoconf-backend` via `docker login` or `make docker-login`\n- run `docker compose up -d`\n\n#### Available endpoints\n\n`/get-vox-token`\n- POST `/get-vox-token`\n- returns voximplant-app user's one-time-token and user data\n- only for authenticated users\n- Body (application/json):\n    - one_time_key - return the one time token in the `ott` field and the `user` data\n\n`/users/:userName`\n- method `GET`\n- returns users' info by name\n- only for authenticated users\n\n### Scenario setup\n\n#### Application and scenario creation\n\nLog in to your Voximplant account [here](https://manage.voximplant.com/auth). Select \"Applications\" in the left menu, and click \"Create\" to create an application. Then, create a scenario for your conference. Read more about scenarios in Voximplant [here](https://voximplant.com/docs/gettingstarted/makeanapp/voxengine).  \n(as this scenario implies a conference without PSTN phone numbers, you don't need to buy a phone number)\n\n#### Complete scenario\n\n```javascript\n// load the conference module and define variables for a conference and participants' counter\nrequire(\"conference\");\nlet conf;\nlet log;\nlet partsCounter = 0;\n// add a handler for the very first call which creates a conference\nVoxEngine.addEventListener(AppEvents.Started, function (event) {\n    log = event.logURL;\n    conf = VoxEngine.createConference({hd_audio: true});\n    conf.addEventListener(ConferenceEvents.Stopped, function (event2) {\n        Logger.write('Conference was stopped!');\n        VoxEngine.terminate();\n    });\n    conf.addEventListener(ConferenceEvents.Started, function (event2) {\n        Logger.write(`Conference is started ID: ${event2.conference.getId()}`);\n    });\n});\n// create another handler for further incoming calls. The handler answers a call and connects it to the conference\nVoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {\n    e.call.answer();\n    e.call.sendMessage(log)\n    partsCounter = partsCounter + 1;\n    const endpoint = conf.add({\n        call: e.call,\n        mode: \"FORWARD\",\n        direction: \"BOTH\", scheme: e.scheme\n    });\n    Logger.write(`New user ${e.callerid} was added as endpoint ID: ${endpoint.id()}`);\n    e.call.addEventListener(CallEvents.Disconnected, function (event2) {\n        partsCounter = partsCounter - 1;\n        if (partsCounter === 0) {\n            setTimeout(checkForTermination, 1000 * 10); // wait for 10 ceconds\n        }\n    });\n});\n// create a function which stops a conference if there are no participants:\nfunction checkForTermination() {\n    if (partsCounter === 0) {\n        conf.stop();\n        conf = null;\n    }\n}\n```\n\n### Client deployment and setup\n\n#### Frontend development tools\n\nWe used the following frameworks in the videoconf-frontend:\n- [Vue 3](https://vuejs.org/guide/introduction.html) for implementing the GUI\n- [Effector](https://effector.dev/docs/glossary) for implementing business logic\n\n#### Configuration file configuration\n\nTo start videoconf, configure the paths for the server and the google web oauth.  \nYou can do this in the `client/src/config.ts` configuration file.\n- baseUrl: your application URL\n- voxAppDomain: your application name, you can see it in the [application list](https://manage.voximplant.com/applications)\n- baseServerUrl: your backend server root url\n- baseAuthRedirectUrl: google account URL for user sign-in/sign-up\n- clientId: web oauth client ID, you can find it in the [web oauth settings](#env-file-configuration)\n\n#### Commands for videoconf-frontend build and development\n\n1. Install modules and dependencies\n\n```\nyarn install\n```\n\n2. Build the project and track changes during the development\n\n```\nyarn serve\n```\n\n3. Build and minimize production\n\n```\nyarn build\n```\n\n### Run your end-to-end tests\n\n```\nyarn test:e2e\n```\n\n### Lints and fixes files\n\n```\nyarn lint\n```\n\n### Customize configuration\n\nSee the [configuration reference](https://cli.vuejs.org/config/).\n\n## UI customization\n\nVideoconf is based on the [SpaceUI](https://www.npmjs.com/package/@voximplant/spaceui) library.\nHere you can learn how to customize the library's UI components as you need.\n\n### UI base customize\n\nLet us try to customize the Button component.\n\nFor example, you want to customize the Invite button:\n\n![img_2.png](./docs-assets/img_2.png)\n\nHere is what the component looks like (ContactList.vue):\n\n```vue\nButton.copy-link-btn(\n  :icon=\"{ spriteUrl: '/image/videoconf-icons.svg', name: 'ic25-add-user', color: '--sui-gray-700'}\"\n  width=\"fill-container\"\n  height=\"48px\"\n  mode='secondary'\n  size=\"l\"\n  @click=\"openPopup\"\n) {{ t('invitePeople.invite') }}\n```\n\n`Button` is a SpaceUI component, and `.copy-link-btn` is a CSS class. The component's properties and the HTML attributes are set in brackets. You can find the list of available properties in the `spaceui.d.ts` file within the SpaceUI folder inside the node_modules.\n\nLet us find the Button class:\n\n```ts\nexport declare class Button extends Vue {\n  size?: ButtonSize;\n  width?: ButtonWidth;\n  icon?: IconProp;\n  mode: ButtonMode;\n  loading?: boolean;\n  hideText?: boolean;\n  // default slot\n  default?: string;\n}\n```\n\nThe properties are:\n- `size` - specifies the button's size. It accepts the following values: `s` - small, `m` - medium, `l` - large\n- `width` - specifies the button's maximum width and accepts the following values:  \n  1)`fill-container` - the button takes full container size  \n  2)`fit-content` - the button adjusts to fit the content\n- `icon` - specifies the button's icon (you need to place the icon to an SVG-sprite)\n\nFind the `IconProp` type to learn what values it accepts:\n\n```ts\nexport type IconProp = string | Icon;\n```\n\nThen find the `Icon` class. It accepts the following object:\n\n```ts\nexport declare class Icon extends Vue {\n  spriteUrl?: string;\n  name: string;\n  color?: string;\n  size?: string | number;\n  width?: string | number;\n  height?: string | number;\n}\n```\n\n`spriteUrl` accepts the path to the SVG-sprite\n`name` accepts the icon id\n\nOther `Button` class properties:\n\n- `height` - HTML height attribute\n- `mode` - specifies the color scheme. For example, if you substitute the `warning` value, the button becomes red, and if you substitute the `flat` value, the button becomes transparent.\n- `loading` - enables the loader animation\n- `hideText` - hides the button text\n- `default` - shows that we can use the slot. It is necessary if you want to put a separate HTML element inside the button (e.g. div, p, span) and assign a CSS class to it; or if you want to put another component inside the button.\n\nYou can also use Vue directives, such as v-if, v-for, v-on, etc. In our example, the button has the directive `@click=\"openPopup\"`. You can add event listeners (click, keyup, submit, etc).  \n`openPopup` - is the button's event handler function. You can add additional functionality to the function, for example, sending statistics to your backend. Alternatively, you can create a decorator for the `openPopup` function and call it an event handler.\n\nLet us try to change the button handler:\n\n```vue\nButton.copy-link-btn(\n  :icon=\"{ spriteUrl: '/image/videoconf-icons.svg', name: 'ic25-hand', color: 'red'}\"\n  width=\"fit-content\"\n  height=\"48px\"\n  mode='outlined'\n  size=\"s\"\n  @click=\"openPopup\"\n) I am button\n```\n\nThis is what the button looks like now:  \n![img_5.png](./docs-assets/img_5.png)\n\nTo stylize the text, use slots:\n\n```vue\nButton.copy-link-btn(\n    :icon=\"{ spriteUrl: '/image/videoconf-icons.svg', name: 'ic25-hand', color: 'red'}\"\n    width=\"fit-content\"\n    height=\"48px\"\n    mode='outlined'\n    size=\"s\"\n    @click=\"openPopup\"\n)\n  .unic-class\n    p.big I am\n    | \u0026nbsp\n    span.red  button\n```\n\nLet's add some CSS.  \nYou can add style sheets at the bottom of the component in the `\u003cstyle scoped\u003e` tag. The scoped attribute means that all the styles are applied within the current component only (currently ContactList.vue):\n\n```postcss\n.unic-class {\n    display: flex;\n    .big {\n      text-transform: uppercase;\n      font-weight: bold;\n    }\n    .red {\n      color: red;\n    }\n}\n```\n\nThis is what the button looks like now:  \n![img_1.png](./docs-assets/img_1.png)\n\nIf the [SpaceUI](https://www.npmjs.com/package/@voximplant/spaceui) customization abilities are not enough, you can add your CSS class to the Button component and create your styles.  \nLet us add a custom `new-class` class and create custom styles.\n\n```vue\nButton.copy-link-btn.new-class(...\n```\n\n```postcss\n.new-class{\n    background-color: greenyellow;\n    border-radius: 20px;\n  }\n```\n\nThis is what the button looks like now:  \n![img_6.png](./docs-assets/img_6.png)\n\nNow let us modify the button's event handler `openPopup`.  \nNow it looks like this: `const openPopup = () =\u003e toggleInvitePopup(true);`\n\nLet us create an abstract function that sends some data to a server:\n\n```vue\nconst getData = (async () =\u003e {\n    const response = await fetch(`https://website.com/api`, {\n      method: 'POST',\n      body: data\n    });\n    const json = await response.json();\n    if (response.status !== 200) throw Error(json.message);\n    return json;\n});\n```\n\nLet us add the `openPopup` function to it:\n\n```vue\nconst openPopup = () =\u003e {\n    getData()\n    return toggleInvitePopup(true)\n};\n```\n\nNow, this button not only opens the invitation popup but also sends data to a server.\n\n### UI advanced customization\n\nIf you do not like an existing UI component, you can create your own.\n\nFor example, this is Message.vue:  \n![img_11.png](./docs-assets/img_11.png)\n\nHere is its usage:\n\n```vue\nMessage(\n  v-for=\"item of chatList\"\n  :name=\"item.name\"\n  :time=\"item.time\"\n  :avatar=\"item.avatar\"\n  :message=\"item.message\"\n  :isPrivate=\"false\"\n)\n```\n\nYou can track that `chatList` receives data from the `client/src/store/chat.ts` file, where the `messages` array is declared:\n\n```vue\ninterface ChatStore {\n  currentConversation?: Conversation; // contain current data of the current conversation\n  messages?: Message[]; // contain current list of messages and reaction with a sender info\n}\n\ninterface Message {\n  name: string;\n  time: string;\n  avatar: string | undefined;\n  message: string;\n}\n```\n\nNow we know what content can be displayed in the component. Let us create a `NewMessage.vue` component and write a custom layout with styles, and accept an `item` property with the `MessageItem` type, which we import from `chat.ts`.\n\nHere is what the component template looks like:\n\n```vue\n\u003ctemplate lang=\"pug\"\u003e\n  .message\n    .info-wrap\n      img.avatar(:src=\"item.avatar\" :alt=\"item.name\")\n      .name-wrap\n        p.name {{ item.name }}\n        p.time {{ item.time }}\n    .message-wrap\n      p.text {{ item.message }}\n\u003c/template\u003e\n```\n\nFor example, you want to improve the standard functionality and visually divide your messages from others' messages. Here is how you can achieve this.\n\nThe current user data is stored in `$users`.\n\n```vue\nsetup(props) {\n    const users = useStore($users);\n    const isLocal = computed(() =\u003e users.value.me?.chatUser.displayName === props.item?.name);\n    return {isLocal, users}\n}\n```\n\nWe address the `$users` store within the `client/src/store/users.ts` file and put the current store state to the `users` variable. When the store updates, the variable updates, too. `isLocal` is a common getter, which can address the store each time when a new message appears, and check if the sender's name corresponds to the current user name. If it does, it marks it with the `isLocal = true` flag and adds a new CSS class, which highlights the message in blue.\n\nLet us add a dynamic class to the template:\n\n```vue\n\u003ctemplate lang=\"pug\"\u003e\n.message(:class=\"{local: isLocal}\")\n  .info-wrap\n    img.avatar(:src=\"item.avatar\" :alt=\"item.name\")\n    .name-wrap\n      p.name {{ item.name }}\n      p.time {{ item.time }}\n  .message-wrap\n    p.text {{ item.message }}\n\u003c/template\u003e\n```\n\nHere is what it looks like now:  \n![img.png](./docs-assets/img.png)  \nAs you can see, your messages are highlighted in blue.\n\nThus, you can rewrite any component the way you like, and change the appearance and functionality.\n\n## Video layouts management\n\nThere are 3 layout types in the current project version. You can find them in the `client/src/helpers/layouts` folder. The `index.ts` file initializes the layout types and provides tools to work with them.\n\n### Layout processing scheme\n\n```mermaid\nflowchart TD\n\nA[($layout)]\nV[SharingButton.vue] --\u003e |toggleSharing| B\nL[changeLayout.vue] --\u003e |changeLayoutType| D\nB[($sharing)] --\u003e |combine gridBuilder| A\nD[($layoutType)] --\u003e |combine gridBuilder| A\nM[ResizeObserver] --\u003e |resizeVideoSlot| F\nF[($canvas)] --\u003e |combine gridBuilder| A\nO[($endpoints)] --\u003e |map| E\nE[($hasEndpointSharing)] --\u003e |combine gridBuilder| A\n\nG --\u003e J{composeVideo}\nJ --\u003e|RenderVideoStore| G\nA --\u003e|composeVideo| G[($renderVideo)] \nH[LayoutsVideo.vue] --\u003e |Render DOM| G\n```\n\n#### store\n\n`$sharing` contains the information about local user's screen sharing\n`$layoutType` contains the information about the current layout (grid by default)\n`$canvas` contains the information about the current nest size\n`$hasEndpointSharing` defines the screen sharing presence flag for conference participants\n`$layout` contains the grid and conference participants' video options\n\n#### event\n\n`toggleSharing` triggers when screen sharing toggles, for example, when a new screen sharing is added (addSharing event)\n`changeLayoutType` triggers when a participant changes its layout\n`resizeVideoSlot` triggers when a video nest changes its resolutions or when ResizeObserver triggers in LayoutsPanel.vue\n\n#### combine function\n\n`gridBuilder` returns a layout from a `gridList` depending on the incoming data from the store:\n- presence of screen sharing\n- video nest resolution\n- layout type\n\n`composeVideo` sends the generated layout settings from `$layout` to the tiler and returns a grid.\n\n#### Scheme description\n\n`$layout` stores the grid configuration options and the participants' priorities.  \nThese options are declared in constants and stored in `$layout` depending on the layout usage conditions.\n\n### Conference layout definition\n\nLayout building options are stored in `$layout` and defined via the `gridBuilder` method.  \nThe method's result depends on 3 major criteria:\n\n1. Video nest resolution. Depending on the resolution, the `getScreenKind` function returns the screen type:\n- large (width \u003e= 1920 \u0026\u0026 height \u003e= 1080)\n- mobile (height \u003e width)\n- default (if not large or mobile)\n\n2. Presence of screen sharing in the conference. If a screen sharing is detected while receiving the grid type, the `Screen` option appears in the `GridKind` function.  \n   In this case, the grid type returns `mobileScreen` instead of `mobile`.\n\n3. User's layout type. This value is stored in `$layoutType`. A user can change the layout type at the start or during the conference. When it happens, the `changeLayoutType` event triggers in the changeLayout.vue component.\n\nBased on these 3 criteria, the `gridBuilder` method returns the `gridList` object, where the key is the current layout type, and the property name contains the video nest resolution and the presence of screen sharing.\n\n### Reordering participants within a layout\n\nLayout reordering functions are located in the `client/src/helpers/layout-reordering` folder.  \nGrid reordering parameters are located in the `reorderTiles` option and used during the grid creation.\n\n#### 1. Participants reordering function\n\nEach layout file has the `ReorderFunction` function which returns the `reorderByVad` function. It manages participants reordering according to the `$conferenceEndpointsState` store by VAD (voice activity detection). To fix the local video, use the `fixLocalVideo` function.\n\n#### 2. The process of reordering participants\n\nYou can reorder participants according to the `$conferenceEndpointsState` store's VAD parameter. The participants' order is defined by triggering the following events: `[ConferenceEvents.vad, ConferenceEvents.mute]`. These events trigger when you toggle/activate the microphone and update the store. Reordering via VAD parameter can happen only within one grid, by changing the priority property. If you want to show the local video with the highest priority, change the `fixLocalVideo`'s position parameter to `\"first\"`.\n\n### Grid overflow checkpoints\n\nGrid overflow checkpoints for each grid type are stored in the `overflowCheckpoint` constant within the `client/src/helpers/layouts/index.ts` file. If you try to fit more participants than the grid allows, you will get a `usersOverflowMock.vue` for each overflown participant. They contain avatars of the overflown participants (up to 3 people) and the number of overflown participants. You can check if a participant fits in a certain grid in the `VideoSlot.vue` file.\n\n### Layout types and descriptions\n\n#### Grid\n\nAll participants have the same size video nests and are displayed one by one. The maximum video nest count is stored in the `overflowCheckpoint.grid` constant within the `index.ts` file.\n\nWhen a participant shares their screen, the screen sharing takes 85% of the screen width. All the participants are displayed as a column of 15% of the screen width to the right (up to 5 participants) and a placeholder for the overflown participants.\n\n![Grid.png](./client/src/assets/images/layouts/grid_layout.jpg)\n\n#### Tribune\n\nThe screen is divided into two areas. The first area takes 70% of the screen width and displays 4 active participants. The rest participants are displayed as a column of 30% of the screen width and a placeholder for the overflown participants.\n\nWhen a participant shares their screen, the screen sharing takes 85% of the screen width. All the participants are displayed as a column of 15% of the screen width to the right (up to 5 participants) and a placeholder for the overflown participants.\n\n#### Demonstration\n\nThe first participant's video takes the whole screen. The second participant's video is displayed at the right bottom corner of the screen on top of the first participant's video with a 29x25% of image size.  \nIf there are more than two participants, a placeholder with information and the participants' count is displayed instead of the second participant's video.\n\nWhen a participant shares their screen, the screen sharing takes the whole screen. The participant's count is displayed in the right bottom corner.\n\n![Demonstration.png](./client/src/assets/images/layouts/demonstration_layout.jpg)\n\n### How to create a custom layout\n\n- create a file with your layout name and place it in the `client/src/helpers/layouts` folder\n- create constants for layout building options. Read more in the [@voximplant/tiler documentation](https://github.com/voximplant/tiler#quickstart)\n- create a `TileInterface` format map, which contains layout building options\n- if you need to reorder participants within a grid, prepare a grid reordering function with `ReorderFunction`, where you can implement participants reordering logic\n- export the function responsible for returning grid settings and reordering logic, for example, `createGridLayout`\n- add your grid to LayoutTypeMap\n- add a special property to `gridList`, where the key is `[LayoutTypeMap.yourLayout]`, and the value is the function responsible for returning grid settings and reordering logic\n- add a special property to `overflowCheckpoint`, where the key is `[LayoutTypeMap.yourLayout]`, and the value is the overflow points for each layout type\n\n### How to delete a layout\n\n- delete the layout from the `LayoutTypeMap` constant\n- delete the TS file from the `client/src/helpers/layouts` folder\n- delete the overflow checkpoints from the `overflowCheckpoint` constant\n- delete the layout from the `gridList` constant\n- delete the layout and its description from the `layoutItemsMobile` and `layoutItems` constants within `ChangeLayout.vue`\n\n### How to customize an existing layout\n\nLet us take a look at layout customization with several examples:\n\n1. If you need to limit the participant number in the main section to 1:\n- find the layout file, which contains the `client/src/helpers/layouts/tribune` grid options\n- find the constant with the necessary grid type, for example, `tribuneDefault`\n- find the first section in the grid building option (priority: 1)\n- find the grid property\n- delete 2,3,4 participants processing, leaving only 1 object:  \n  `{ fromCount: 1, toCount: 1, colCount: 1, rowCount: 1, margin: 12 }`\n- change the participants overflow checkpoint value in `overflowCheckpoint.[LayoutTypeMap.tribune].default` to 5 (1 participant from `priority: 1` and 4 participants from `priority: 2`)\n\nDone. Now the first section has 1 participant only and the rest participants are in the column to the right.\n\n2. If you want to limit the grid layout participants to N:\n- find the layout file, which contains the `client/src/helpers/layouts/grid` grid options\n- find the constant with the necessary grid type, for example, `grideMobile`\n- find the grid property\n- delete the excess objects, leaving only N objects you need:\n\n```javascript\ngrid: [\n        { fromCount: 1, toCount: 1, colCount: 1, rowCount: 1, margin: 0 },\n        { fromCount: 2, toCount: 2, colCount: 1, rowCount: 2, margin: 5 },\n        { fromCount: 3, toCount: N, colCount: 2, rowCount: 2, margin: 5 }\n]\n```\n\n- change the participants overflow checkpoint value in `overflowCheckpoint.[LayoutTypeMap.grid].default` to N\n\nDone. Now in the mobile version, there are only N participants displayed, and if there are more participants, a placeholder with the number of overflown participants appears.\n\n## Mirror store\n\nMirror store processes local media: audio and video.\n\n```mermaid\nflowchart TD\n\nA[($mirrorStore)]\nV[Vue component] --\u003e B{requestMirrorStream}\nB --\u003e|updateLocalEndpoint| C[($endpointEventList)]\nB --\u003e A\nD[AudioButton] --\u003e |toggleMirrorAudioStream| A\nF[VideoButton] --\u003e |toggleMirrorVideoStream| A\n\nA --\u003e J{deviceWatcher}\nJ--\u003eA\nJ --\u003e|setActiveDevices| G[($devicesStore)]\n```\n\nCall `requestMirrorStream` to get tracks from local audio and video devices. It also processes possible errors, device changing, and microphone activity visualization. Call it each time you need to change the camera/microphone/headphones before or during a conference.\n\nCall `toggleMirrorAudioStream` each time you mute/unmute the microphone for conference participants.\n\nCall `toggleMirrorVideoStream` each time when you enable/disable the local camera device. Sending video streams to conference participants depends on this event.\n\n## Device store\n\nThis store processes the information about all the available local media devices, all the active media devices, and the information on the current local audio/video stream and its quality. If you need to get all user devices, change the microphone, speaker, or camera, mute audio or disable the local camera, take a look at the following events.\n\n```mermaid\nflowchart RL\n    \nA[($devices)]\nJ[Join.vue] --\u003e |getDevices| A\nJ[Join.vue] --\u003e |toggleVideoDisabled| A\nE[ondevicechange] --\u003e S{setActiveDevices}\nV[Video.vue] --\u003e S\nS--\u003e A\nV --\u003e |setVideoQuality| A\nAB[AudioButton.vue] --\u003e |toggleAudioEvent| A\nAB[AudioButton.vue] --\u003e |selectSpeakerDevice| A\nVB[VideoButton.vue] --\u003e |toggleVideoEvent| A\n```\n\nThe `getDevices` event returns all available user devices, such as the microphone, speaker, and camera. This event also specifies the default and active system devices.  \nThe `setActiveDevices` event updates the information about active devices but does not change the media stream. To change the media stream, use `requestMirrorStream`.  \nTo change the speaker device, use the `selectSpeakerDevice` event. It updates the store and changes the current user's speaker.  \nThis store also processes connecting new system devices and disconnecting the existing ones. When you connect a new sound device during a conference, the store chooses it as the active one. When you disconnect an active sound device during a conference, you need to choose a new one from the existing device list. When you connect a new camera during a conference, the store just updates the device list but does not choose the device as the active one. When you disconnect a camera during a conference, the store stops sending the video stream.\n\nThe `toggleAudioEvent` and `toggleVideoEvent` events process the microphone and camera usage status accordingly. To toggle the microphone and camera, use the `toggleMirrorAudioStream` and `toggleMirrorVideoStream` events.\n\nThe `toggleVideoDisabled` event allows developers to disable a participant's camera permanently, in case of camera unavailability.\n`setVideoQuality` allows developers to choose the video quality for outbound video. If you choose the quality lower than 480p, the simulcast will be disabled.\n\n## Chat store\n\nThe $chatContent store generates the conference ID and stores the information about the current conversation and its messages.\nAdditionally, it provides the ability to send user reactions.\n\n```mermaid\nsequenceDiagram\n    participant Alice\n    participant Voximplant\n    participant Bob\n    participant John\n    Alice-\u003e\u003eVoximplant: createConversation\n    Voximplant--\u003e\u003eAlice: currentConversation\n    Alice-\u003e\u003eVoximplant: fetchInitialData\n    Voximplant--\u003e\u003eAlice: Users, Messages\n    Bob-\u003e\u003eVoximplant: joinConversation\n    Voximplant--\u003e\u003eBob: currentConversation\n    Bob-\u003e\u003eVoximplant: fetchInitialData\n    Voximplant--\u003e\u003eBob: Alice info\n    Voximplant--\u003e\u003eAlice: Bob joined\n    Bob-\u003e\u003eVoximplant: subscribeChat\n    Alice-\u003e\u003eVoximplant: sendMessage ('Hi, Bob. Nice to see you!')\n    Voximplant--\u003e\u003eBob: MessageReceived from Alice ('Hi, Bob. Nice to see you!')\n    John-\u003e\u003eVoximplant: joinConversation\n    Voximplant--\u003e\u003eJohn: currentConversation\n    John-\u003e\u003eVoximplant: fetchInitialData\n    John-\u003e\u003eVoximplant: subscribeChat\n    Voximplant--\u003e\u003eJohn: Bob and Alice info\n    Voximplant--\u003e\u003eJohn: previous messages (MessageReceived from Alice ('Hi, Bob. Nice to see you!'))\n    Voximplant--\u003e\u003eAlice: John joined\n    Voximplant--\u003e\u003eBob: John joined\n    Alice-\u003e\u003eVoximplant: sendMessage ('Hi, John. How are you?')\n    Voximplant--\u003e\u003eJohn: MessageReceived from Alice ('Hi, John. How are you?')\n    Voximplant--\u003e\u003eBob: MessageReceived from Alice ('Hi, John. How are you?')\n```\n\nTo start a chat, create a new conversation via the `createChat` event, or join an existing chat via the `joinChat` event. These events return the current conversation, but you need to additionally call the `fetchInitialData` event to retrieve all existing participants and messages.\n\nThe `subscribeChat` event subscribes to the conversation events, such as receiving new messages, adding new participants, and others.  \nTo send a message into the conversation, call the `sendMessageToAll` event.\nCall the `updateChatContent` event to update the chat after some changes happen, like receiving a new message or a reaction.\n\n```mermaid\nflowchart TD\nA[($meeting)]--\u003e|createConversation/joinConversation| B[($chatContent)]\nC --\u003e|updateReaction| F[($reactions)]\nC --\u003e B\nB --\u003e E{subscribeChat}\nE --\u003e C{updateChatContent}\nE --\u003e|updateUsers| D[($users)]\nB --\u003e G{fetchInitialData}\nG --\u003e |updateUsers| D\nG --\u003e C\n```\n\nAfter finishing the conference, you need to clear the store via the `clearChat` event, which unsubscribes from all the events and cleares the store.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoximplant%2Fsolutions-videoconference","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoximplant%2Fsolutions-videoconference","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoximplant%2Fsolutions-videoconference/lists"}