Qonsoll video
Integration
npm i @qonsoll/qvideo
- Import styles into global index.js file:
import "@qonsoll/qvideo/dist/styles/styles.css"
- In file where you want to use Recorder or Player insert import:
import { Recorder, Player, VideoSnapshot } from "@qonsoll/qvideo"
- After import you can use Recorder, Player or VideoSnapshot components:
<div style={{ height: #some_height }}>
<Recorder apiKey={#some_api_key} autoStart />
</div>
<div style={{ height: #some_height, width: #some_width }}> (❗ width and height are required to display player)
<Player apiKey={#some_api_key} videoToken={#some_token}/>
</div>
<div style={{ height: #some_height, width: #some_width }}>
<VideoSnapshot apiKey={#some_api_key} videoToken={#some_token}/>
</div>
- Here apiKey is key of your application at QVideo service, videoToken is a token that Recorder return after successful video upload. You can extract videoToken using onUpload property of Recorder which is a callback function that pass videoToken of uploaded video as function argument. Chapters is array with all added chapters on record (or null if they aren't any) For example:
<Recorder
apiKey={#some_api_key}
autoStart
onUpload={(videoToken, chapters) => {console.log('video token:', videoToken, chapters)}}
/>
This code will log video token and chapters of uploaded video when it is successfully uploaded to database and is available to be viewed in Player
Recorder properties
-
❗ apiKey (required prop) -
❗ autoStart - recorder props to init camera and micro access on component mounted (required prop) - customOptions - possibility to override default player options
- countdownDuration - add countdown between Record button press and recording start (in sec)
- circle - toggle displaying recorder/player in circle mode or default
- videoDuration - change video record maxLength (default 30sec)
- translations - use to override preset translations, tooltips, etc.
- language (example language={'en'})
- spinnerSize (sm, md, lg. Default "md")
- spinnerText
- isCameraConfig - hide/show Camera selection button (allow to select connected video device to record with)
- isMicroConfig - hide/show Microphone selection button (allow to select connected microphone device to record with)
- isScreenRecord- hide/show Screen record button (allow to record video from screen or browser etc.)
- isNotes - hide/show Notes button (add text-area that allow make small notes for speech record)
- isCancel - hide/show Cancel button
- isUpload - hide/show Upload button (allow to upload video from device/record from camera and preview it)
- isLibrary - hide/show Library button
- isPiP - hide/show Picture-in-Picture mode button (enable possibility to record with small draggable screen that display recorded content)
- isLink - hide/show add video via links (youtube, vimeo) button (allow to upload videos from youtube/vimeo and preview them before upload).
❗ To play video from external source (youtube, vimeo) first it must be uploaded via Link, receive videoToken and pass it to the Player - isChapters - hide/show Chapters creation button (allow to split recorded video to chapters with separating player progressbar to same parts with tooltip and possibility to stop video on Chapter ends
Recorder Callbacks
- onRecordStart
- onUpload - returns videoToken and chapters after video upload
- onChaptersChange - returns chapters while changed
- onRecordStop
- onRecordApprove
- onRecordReject
- onRecordReplay
- onLibraryClick
- onNotesClick
- onCancelClick
- onScreenRecordClick
- onCameraRecordClick
- onPipClick
- onLinkClick
Player properties
-
❗ apiKey (required prop) -
❗ videoToken (required prop) - userId - (
❗ required prop to start collect data about user interaction with video) - customOptions - possibility to override default player options
- circle - toggle displaying player in circle mode or default
- controledPlayerState - object with video state used to control video
- playButtonSize = "lg" || "md" || "sm" (default md)
- spinnerSize (sm, md, lg. Default "md")
- spinnerText
- hideSpinner
- hideProgressBar
- hideControls (hide play/pause buttons, progress bar on player)
Player Callbacks
- onPlayerLoaded - callback function after player initialization which returns player instance and video instance
- onRemoteControlReady - callback function after player loading which checks play errors and returns true if video can be played, false - if navigator blocked auto play
- onChaptersLoaded - callback function that will be called after chapters are loaded and formed up into array of object
- onCHaptersChange
- onPlayerStateChange - callback for remote sharing|control of video which returns video state changes
Remote control
Extract player state
function App() {
const [controledPlayerState, setControledPlayerState] = useState()
const onPlayerStateChange = (playerState) => {
setControledPlayerState((prev) => ({ ...(prev || {}), ...playerState }))
}
return (
<div style={{ height: #some_height }}>
<Player
apiKey={#some_api_key}
videoToken={#some_token}
onPlayerStateChange={onPlayerStateChange}
/>
</div>
)
}
export default App
Supported controls
- played (is used to start|pause video)
- currentTime (is used to rewind the video to the specified time)
- isFullScreen (tmp disabled)
- muted (tmp disabled)
Control player using state
function App() {
const [controledPlayerState, setControledPlayerState] = useState()
return (
<div style={{ height: #some_height, width: #some_width }}>
<button onClick={() => setControledPlayerState({ played: true })}>
play
</button>
<button onClick={() => setControledPlayerState({ played: false })}>
pause
</button>
<button onClick={() => setControledPlayerState({ currentTime: '5.00' })}>
5.00
</button>
<button onClick={() => setControledPlayerState({ isFullScreen: true })}>
fullScreen
</button>
<Player
apiKey={#some_api_key}
videoToken={#some_token}
controledPlayerState={controledPlayerState}
/>
</div>
)
}
export default App
Statistic
Resource URL
https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/
API Description
- Get video statistics for the specified userId and videoToken:
{
method: GET
url: https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/<USER_ID>/videoStatistics/<VIDEO_TOKEN>
headers: { appId: <API_KEY> }
returns: JSON object that contains field data where statistic entry data is stored
}
- Get statistics for all videos for the specified userId:
{
method: GET
url: https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/<USER_ID>/videoStatistics/
headers: { appId: <API_KEY> }
returns: JSON object that contains field data where array of statistic entries is stored
}
- Get statistics for all videos for the specified videoToken:
{
method: GET
url: https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/videoStatistics/<VIDEO_TOKEN>
headers: { appId: <API_KEY> }
returns: JSON object that contains field data where array of statistic entries is stored
}
- Get general video statistic:
{
method: GET
url: https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/generalVideoStatistics/<VIDEO_TOKEN>
headers: { appId: <API_KEY> }
returns: JSON object that contains field data where array of statistic entries is stored
}
Example Requests
- Get video statistics for the specified userId and videoToken:
const fetchUri = `https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/${<USER_ID>}/videoStatistics/${<VIDEO_TOKEN>}`
const headers = { appId: <API_KEY> }
fetch(fetchUri, { headers })
.then((data) => data.json())
.then((data) => console.log(data))
- Get statistics for all videos for the specified userId:
const fetchUri = `https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/${<USER_ID>}/videoStatistics/`
const headers = { appId: <API_KEY> }
fetch(fetchUri, { headers })
.then((data) => data.json())
.then((data) => console.log(data))
- Get statistics for all videos for the specified videoToken:
const fetchUri = `https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/videoStatistics/${<VIDEO_TOKEN>}`
const headers = { appId: <API_KEY> }
fetch(fetchUri, { headers })
.then((data) => data.json())
.then((data) => console.log(data))
- Get general video statistic:
const fetchUri = `https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/generalVideoStatistics/${<VIDEO_TOKEN>}`
const headers = { appId: <API_KEY> }
fetch(fetchUri, { headers })
.then((data) => data.json())
.then((data) => console.log(data))
Example Response
- User statistic:
{
"id": "06_01_2222_USER_TEST_vfCLVhBAYBGBXfz32CvE",
"completedViewsCount": 0, // counts the number of full (100%) video views
"firstViewDate": {
"_seconds": 1642433020,
"_nanoseconds": 50000000
},
"userId": "06_01_2222_USER_TEST",
"sessions": [
{
"sessionExtraData": null,
"secondsPlayed": "2.30",
"startDate": {
"_seconds": 1642433013,
"_nanoseconds": 447000000
},
"id": "fe756ad3-3763-4524-8a48-3989f535702a",
"isPlaybackCompleted": false,
"location": null,
"deviceType": "desktop",
"browserName": "Chrome",
"browserVersion": "97",
"qVideoVersion": "0.1.28"
}
],
"viewsByDate": {
"17-01-2022": 1
},
"lastViewPercentPlayed": "0%", // the percent of video playback when video was stopped during the last session
"videoDuration": 465.201,
"lastSession": {
"startDate": { // datetime of session start
"_seconds": 1642433013,
"_nanoseconds": 447000000
},
"browserVersion": "97",
"qVideoVersion": "0.1.28",
"secondsPlayed": "2.30", // the time during which playback was stopped during the session
"deviceType": "desktop",
"browserName": "Chrome",
"isPlaybackCompleted": false, // indicates that video was played at least of 70%
"sessionExtraData": null,
"id": "fe756ad3-3763-4524-8a48-3989f535702a", // uniq session id
"location": null // user address (currently not fetching)
},
"videoId": "vfCLVhBAYBGBXfz32CvE",
"viewsByDeviceType": {
"desktop": 1
},
"lastViewSecondsPlayed": "2.30", // the time during which playback was stopped during the last session
"uncompletedViewsCount": 1, // counts the number of uncompleted video views
"totalViewsCount": 1 // counts the number of all video views
}
- General video statistic:
{
"generalInfo": {
"fullPlaybacksCount": 1,
"incompletePlaybacksCount": 48,
"viewsCount": 49,
"rewindsCount": 62,
"averagePlaybackInSeconds": 12,
"pausesCount": 75,
"averagePausesCount": 1,
"averagePlaybackInPercents": 21,
"averageRewindsCount": 4
},
"id": "RBOV0el5XD65xVhjpDZj",
"videoInfo": {
"duration": 30.00010000014305,
"size": null,
"uploadDate": {
"_seconds": 1642500011,
"_nanoseconds": 84000000
},
"videoId": "BwWO65EJOtfmVTEMFV9X"
},
"chaptersInfo": {
"chapterRewindsCount": {
"Intro": 3,
"End": 2,
"Chapter 2": 10,
"Chapter 3": 12,
"Chapter 1": 6
},
"mostFrequentlyRewindedChapter": "Chapter 3",
"chapters": [
{
"startTimeFormatted": "00:00",
"shouldStop": false,
"startTime": 0,
"title": "Intro"
},
{
"shouldStop": false,
"startTime": 5,
"startTimeFormatted": "00:05",
"title": "Chapter 1"
},
{
"startTimeFormatted": "00:10",
"shouldStop": false,
"title": "Chapter 2",
"startTime": 10
},
{
"startTimeFormatted": "00:15",
"startTime": 15,
"title": "Chapter 3",
"shouldStop": false
},
{
"startTimeFormatted": "00:25",
"title": "End",
"startTime": 25,
"shouldStop": false
}
]
},
"viewsInfo": {
"viewsByBrowserCount": {
"Chrome": 48,
"Opera": 1
},
"viewsByDeviceTypeCount": {
"mobile": 3,
"tablet": 0,
"desktop": 46
},
"viewsByDateCount": {
"20-01-2022": 25,
"19-01-2022": 2,
"21-01-2022": 5,
"18-01-2022": 17
}
}
}
Chapters
- Chapters format:
{
startTime - time when chapter starts,
title - name of the chapter,
startTimeFormatted - formatted time to show on UI, for instance: 01:30 for 1 minute and 30 seconds; 02:15:10 for 2 hours, 15 minutes and 10 seconds
onClick - function that perform jump to this chapter on video,
shouldStop - boolean which controls player stop on chapter end
}
-
For adding chapters you need to provide isChapters property to Recorder
-
For editing chapters you need to provide isChaptersEditable property to Player. To get edited state use Players callback onChaptersChange
-
Use the video instance to rewind the video to the selected chapter or custom time
const [player, setPlayer] = React.useState()
const seekTo = (time) => {
player?.currentTime?.(time)
player?.play?.()
}
return (
<Player
onPlayerLoaded={(player) => setPlayer(player)}
...
/>
)
Translations
Qonsoll translations
- You can use qonsoll translations provider to add translations:
npm install @qonsoll/translation@0.3.7
import { TranslationProvider } from '@qonsoll/translation'
const AppWrapper = ({children}) => {
return (
<TranslationProvider
languages={LANGUAGES}
defaultLanguage={DEFAULT_LANGUAGE}
currentApp={CURRENT_APP}
db={firebase.database()}
>
{children}
</TranslationProvider>
)
}
- You can provide to Player and Recorder (VideoSnapshot using only error codes) phrases and language to use default translations:
const phrases = useMemo(() => ({
countdownMessage: 'Record in',
startRecordMsg: 'Press record to start',
appleDevicesChromeErrorMsg: 'This device allow only upload videos',
appleDevicesChromeErrorPressUpload: 'Press Upload button to start',
reviewRecordMsg: 'Like it',
notesPlaceholder: 'Sketch here a short script of the speech',
chaptersPlaceholder: 'Chapters creating example',
audioDeviceSelectTooltip: 'Select microphone',
videoDeviceSelectTooltip: 'Select camera',
screenRecordTooltip: 'Screen record',
cameraRecordTooltip: 'Switch to camera',
uploadToolTip: 'Upload a video file',
libraryToolTip: 'Pick a video from the library',
linkToolTip: 'Add video via link',
cancelTooltip: 'Cancel',
notesTooltip: 'Notes',
pipTooltip: 'Picture in picture',
chaptersBtnTooltip: 'Chapters',
chaptersCreateNewBtnTooltip: 'Add new chapter',
chapterCancelBtnText: 'Cancel',
chaptersHeader: 'Chapters',
chapterApproveBtnText: 'Approve',
chapterAddBtnText: 'Add',
chaptersModalFormatError: 'Wrong chapter format at line',
videoLinkModalHeader: 'Video Link',
videoLinkModalInputPlaceholder: 'Insert link here',
videoLinkWrongLinkMsg: 'Provided string is not a link',
videoLinkServerSupportError: 'This video service is not supported',
chapterInputPlaceholder: 'Enter chapter name',
noChaptersMessage:
'No chapters on this video, click "+" button below to create new chapter',
pauseSwitcherTooltip: 'Pause on chapter end',
deleteChapterTooltip: 'Delete chapter',
chapter: 'Chapter',
chapterStartTimeTitle: 'Start time',
chapterPauseTitle: 'Pause',
chapterNameTitle: 'Chapter name',
// Player
noInternetErrorMsg: 'Internet connection is weak',
wrongInputErrorMsg: 'Invalid input format',
allowRemoteMsg: 'Click here to allow remote control'
}), []) // phrases using to override default tranlsations
<Component(Player || Recorder)
phrases={phrases}
language="en" // Available options ['en', 'no', 'ua']
...
/>