Совместный просмотр
Совместный просмотр позволяет участникам звонка смотреть видео вместе с синхронизацией воспроизведения.
Жизненный цикл
┌─────────────────┐
│ addMovie() │ ← Добавить видео
└────────┬────────┘
│
┌────────▼────────┐
│ onSharedMovieInfo │ ← Получить информацию о видео
└────────┬────────┘
│
┌────────▼────────┐
│updateDisplayLayout│ ← Запросить MediaStream
└────────┬────────┘
│
┌────────▼────────┐
│ onLive │ ← Получить MediaStream
└────────┬────────┘
│
Воспроизведение
(onLiveUpdate)
│
┌────────▼────────┐
│ removeMovie() │ ← Удалить видео
└─────────────────┘Добавление видео
// Добавить видео по ID
await SDK.addMovie({
movieId: 123456,
gain: 0.5 // Начальная громкость (0-1)
});Метаданные
Желательно передать IMovieMetaData в SDK.addMovie. В противном случае бекенд попробует получить её сам, но результат не всегда будет предсказуемым.
Получение информации о видео
После добавления видео все участники получают уведомление:
await SDK.init({
// ...
// Для инициатора
onLocalSharedMovieInfo: (userId, movieInfo, roomId) => {
console.log('Видео добавлено:', movieInfo.title);
console.log('Movie ID:', movieInfo.movieId);
// Запросить MediaStream
requestMovieStream(userId, movieInfo);
},
// Для остальных участников
onRemoteSharedMovieInfo: (userId, movieInfo, roomId) => {
console.log('Участник добавил видео:', movieInfo.title);
// Запросить MediaStream
requestMovieStream(userId, movieInfo);
}
});Запрос MediaStream
После получения информации о видео нужно запросить поток:
import { MediaType } from '@vkontakte/calls-sdk';
function requestMovieStream(userId, movieInfo) {
SDK.updateDisplayLayout([{
uid: userId,
mediaType: MediaType.MOVIE,
width: 1920,
height: 1080,
streamName: movieInfo.movieId.toString()
}]);
}Получение MediaStream
await SDK.init({
// ...
// Для инициатора
onLocalLive: (userId, data) => {
if (data.stream) {
videoElement.srcObject = data.stream;
}
},
// Для остальных участников
onRemoteLive: (userId, data) => {
if (data.stream && data.mediaType === MediaType.MOVIE) {
videoElement.srcObject = data.stream;
}
}
});Управление воспроизведением
Пауза и воспроизведение
// Поставить на паузу
await SDK.updateMovie({
movieId: activeMovieId,
pause: true
});
// Возобновить воспроизведение
await SDK.updateMovie({
movieId: activeMovieId,
pause: false
});Управление громкостью
// Установить громкость (0-1)
await SDK.updateMovie({
movieId: activeMovieId,
gain: 0.8 // 80%
});
// Выключить звук
await SDK.updateMovie({
movieId: activeMovieId,
mute: true
});
// Включить звук
await SDK.updateMovie({
movieId: activeMovieId,
mute: false
});Перемотка
// Перемотать на определённое время (в секундах)
await SDK.updateMovie({
movieId: activeMovieId,
offset: 120 // К 2-й минуте
});Отслеживание изменений состояния
Изменения состояния видео приходят в коллбэках:
await SDK.init({
// ...
// Для инициатора
onLocalLiveUpdate: (userId, data) => {
console.log('Громкость:', data.gain);
console.log('На паузе:', data.pause);
console.log('Звук выключен:', data.mute);
console.log('Позиция:', data.offset);
},
// Для остальных участников
onRemoteLiveUpdate: (userId, data) => {
updatePlayerUI(data);
}
});Удаление видео
// Удалить видео
await SDK.removeMovie(movieId);После удаления участники получают уведомление:
await SDK.init({
// ...
onLocalSharedMovieStoppedInfo: (movieInfo, roomId) => {
// Видео удалено (для инициатора)
stopMovieStream();
},
onRemoteSharedMovieStoppedInfo: (userId, movieInfo, roomId) => {
// Видео удалено другим участником
stopMovieStream();
}
});
function stopMovieStream() {
// Остановить поток
SDK.updateDisplayLayout([{
uid: userId,
mediaType: MediaType.MOVIE,
stopStream: true
}]);
videoElement.srcObject = null;
}Управление доступом
Можно ограничить управление видео только для администраторов:
import { ConversationFeature, UserRole } from '@vkontakte/calls-sdk';
// Разрешить управление только админам и создателю
await SDK.enableFeatureForRoles(
ConversationFeature.MOVIE_SHARE,
[UserRole.CREATOR, UserRole.ADMIN]
);
// Разрешить всем
await SDK.enableFeatureForRoles(
ConversationFeature.MOVIE_SHARE,
[]
);Полный пример
import * as SDK from '@vkontakte/calls-sdk';
import { MediaType } from '@vkontakte/calls-sdk';
let activeMovieId = null;
let movieParticipantId = null;
await SDK.init({
// ...
onLocalSharedMovieInfo: (userId, movieInfo, roomId) => {
activeMovieId = movieInfo.movieId;
movieParticipantId = userId;
requestMovieStream(userId, movieInfo);
},
onRemoteSharedMovieInfo: (userId, movieInfo, roomId) => {
activeMovieId = movieInfo.movieId;
movieParticipantId = userId;
requestMovieStream(userId, movieInfo);
},
onLocalLive: (userId, data) => {
if (data.stream) {
playMovie(data.stream);
}
},
onRemoteLive: (userId, data) => {
if (data.stream && data.mediaType === MediaType.MOVIE) {
playMovie(data.stream);
}
},
onLocalLiveUpdate: (userId, data) => updateUI(data),
onRemoteLiveUpdate: (userId, data) => updateUI(data),
onLocalSharedMovieStoppedInfo: () => stopMovie(),
onRemoteSharedMovieStoppedInfo: () => stopMovie()
});
function requestMovieStream(userId, movieInfo) {
SDK.updateDisplayLayout([{
uid: userId,
mediaType: MediaType.MOVIE,
width: 1920,
height: 1080,
streamName: movieInfo.movieId.toString()
}]);
}
function playMovie(stream) {
const video = document.getElementById('movie-player');
video.srcObject = stream;
video.play();
}
function updateUI(data) {
// Обновить UI плеера
document.getElementById('volume').value = data.gain * 100;
document.getElementById('pause-btn').textContent = data.pause ? '▶️' : '⏸️';
}
function stopMovie() {
activeMovieId = null;
const video = document.getElementById('movie-player');
video.srcObject = null;
}
// Управление
async function togglePause() {
if (!activeMovieId) return;
const isPaused = document.getElementById('pause-btn').textContent === '▶️';
await SDK.updateMovie({
movieId: activeMovieId,
pause: !isPaused
});
}
async function setVolume(value) {
if (!activeMovieId) return;
await SDK.updateMovie({
movieId: activeMovieId,
gain: value / 100
});
}Особенности
Права на управление
По умолчанию управлять видео может только тот, кто его добавил, и администраторы звонка.
Важно
Не забудьте вызвать updateDisplayLayout с stopStream: true после удаления видео, чтобы корректно освободить ресурсы.