npm package: @qonsoll/qvideo
Description: - [Integration](#Integration) - [Recorder properties](#Recorder-properties) - [Player properties](#Player-properties) - [Remote control](#Remote-control) - [Statistic](#Statistic) - [Chapters](#Chapters) - [Translations](#Translations)
Author: qonsoll
Latest version: 0.1.73

# Qonsoll video
- [Integration](#Integration)
- [Recorder properties](#Recorder-properties)
- [Player properties](#Player-properties)
- [Remote control](#Remote-control)
- [Statistic](#Statistic)
- [Chapters](#Chapters)
- [Translations](#Translations)

## Integration

```bash
npm i @qonsoll/qvideo
```

1. Import styles into global index.js file:

```jsx
import "@qonsoll/qvideo/dist/styles/styles.css"
```

2. In file where you want to use Recorder or Player insert import:

```jsx
import { Recorder, Player, VideoSnapshot } from "@qonsoll/qvideo"
```

3. After import you can use Recorder, Player or VideoSnapshot components:

```jsx

<div style={{ height: #some_height }}>
  <Recorder apiKey={#some_api_key} autoStart />
</div>
```

```jsx
<div style={{ height: #some_height,  width: #some_width }}> (&#10071; width and height are required to display player)
  <Player apiKey={#some_api_key} videoToken={#some_token}/>
</div>
```

```jsx
<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:

```jsx
<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

- &#10071; apiKey (required prop)
- &#10071; 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).
   &#10071; 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

- &#10071; apiKey (required prop)
- &#10071; videoToken (required prop)
- userId - (&#10071; 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

```jsx
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

```jsx
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
&#10071; userId is required props to add and get statistic data from DB.
In order for the system to start recording statistics on the user, you need to provide the userId to the player!

### Resource URL
```https://us-central1-qonsoll-video-transcoder.cloudfunctions.net/userStatistics/```

### API Description
- Get video statistics for the specified **userId and videoToken**:
```js
  {
    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**:
```js
  {
    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**:
```js
  {
    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:
```js
  {
    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**:
```js
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**:
```js
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**:
```js
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:
```js
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:
```js
{
    "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:
```js
{
    "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:
```js
  { 
    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
```js
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:
```bash
npm install @qonsoll/translation@0.3.7
```

```js
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:
```js
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']
  ...
 />
```