Skip to content

Instantly share code, notes, and snippets.

@pierreabreup
Last active January 21, 2021 02:29
Show Gist options
  • Save pierreabreup/4d02d55a7f231cfd1e563f1e5a8dc53f to your computer and use it in GitHub Desktop.
Save pierreabreup/4d02d55a7f231cfd1e563f1e5a8dc53f to your computer and use it in GitHub Desktop.
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
class H5PVideoState {
constructor(h5pVideoClass) {
this.h5pVideoClass = h5pVideoClass
this.lastState = null
this.lastCurrentTime = null
//bug native xAPI Video State currentTime
this.lastCurrentTimeForBuffering = null
}
buildStatementsWith = ({ state, statement, currentTime, title, duration }) => {
const statements = []
statement.result.extensions = statement.result.extensions || {}
statement.result.extensions[
'http://id.tincanapi.com/extension/duration'
] = this.secondsAsISO8601Duration(duration)
statement.object.definition.type = 'http://activitystrea.ms/schema/1.0/video'
statement.object.definition.name = { 'pt-BR': title }
statement.result.completion = false
switch (state) {
case this.h5pVideoClass.PLAYING:
this.lastCurrentTimeForBuffering = this.lastCurrentTime
if (this.lastState === this.h5pVideoClass.BUFFERING) {
break //avoid to sent statement when video has buffered
}
statement.result.extensions[
'http://id.tincanapi.com/extension/starting-point'
] = this.secondsAsISO8601Duration(currentTime)
if (this.lastState === null) {
statement.verb.id = 'http://activitystrea.ms/schema/1.0/start'
statement.verb.display = { 'pt-BR': 'started' }
} else {
statement.verb.id = 'http://activitystrea.ms/schema/1.0/play'
statement.verb.display = { 'pt-BR': 'played' }
}
statements.push(statement)
break
case this.h5pVideoClass.PAUSED:
if (currentTime === duration) {
break //avoid to sent statement when video has ended
}
statement.result.extensions[
'http://id.tincanapi.com/extension/ending-point'
] = this.secondsAsISO8601Duration(currentTime)
if (this.lastState === this.h5pVideoClass.PLAYING) {
const watchedStatement = this.cloneStatement(statement)
watchedStatement.result.extensions[
'http://id.tincanapi.com/extension/starting-point'
] = this.secondsAsISO8601Duration(this.lastCurrentTime)
watchedStatement.verb.id = 'http://activitystrea.ms/schema/1.0/watch'
watchedStatement.verb.display = { 'pt-BR': 'watched' }
statements.push(watchedStatement)
}
statement.verb.id = 'http://id.tincanapi.com/verb/paused'
statement.verb.display = { 'pt-BR': 'paused' }
statements.push(statement)
break
case this.h5pVideoClass.ENDED:
statement.result.completion = true
statement.verb.id = 'http://activitystrea.ms/schema/1.0/complete'
statement.verb.display = { 'pt-BR': 'completed' }
statements.push(statement)
break
case this.h5pVideoClass.BUFFERING:
//bug native xAPI Video State currentTime
const endingPoint = currentTime
let startingPoint = this.lastCurrentTime
if (
this.lastCurrentTimeForBuffering &&
this.lastCurrentTimeForBuffering < startingPoint
) {
startingPoint = this.lastCurrentTimeForBuffering
}
//avoid xAPI wrong time values
if (startingPoint === endingPoint) {
break
}
statement.result.extensions[
'http://id.tincanapi.com/extension/starting-point'
] = this.secondsAsISO8601Duration(startingPoint)
statement.result.extensions[
'http://id.tincanapi.com/extension/ending-point'
] = this.secondsAsISO8601Duration(endingPoint)
if (this.lastCurrentTime > currentTime) {
statement.verb.id = 'http://activitystrea.ms/schema/1.0/return'
statement.verb.display = { 'pt-BR': 'returned' }
statements.push(statement)
} else {
statement.verb.id = 'http://id.tincanapi.com/verb/skipped'
statement.verb.display = { 'pt-BR': 'skipped' }
statements.push(statement)
}
break
default:
// do nothing
}
this.lastState = state
this.lastCurrentTime = currentTime
return statements
}
secondsAsISO8601Duration = seconds => {
return `PT${Number.parseFloat(seconds).toFixed(2)}S`
}
cloneStatement = statement => {
return JSON.parse(JSON.stringify(statement))
}
}
const H5pContent = ({ iframeSrc }) => {
const handleContentRef = dom => {
if (dom) {
dom.onload = () => {
dom.contentWindow.H5P.externalDispatcher.on('xAPI', function(event) {
console.log('INITIAL STATEMENT ON externalDispatcher')
const h5pClass = contentWindow.H5P
if (h5pClass.instances?.length && h5pClass.instances[0]?.video) {
const h5pVideoInstance = h5pClass.instances[0]
const h5pVideoState = new H5PVideoState(h5pClass.Video)
h5pVideoInstance.video.on('stateChange', function(event) {
const videoStatement = h5pVideoInstance.getXAPIData().statement
console.log('INITIAL STATEMENT ON video stateChange')
console.log(videoStatement)
h5pVideoState
.buildStatementsWith({
state: event.data,
statement: videoStatement,
currentTime: h5pVideoInstance.video.getCurrentTime(),
title: h5pVideoInstance.getTitle(),
duration: h5pVideoInstance.video.getDuration(),
})
.forEach(statement => {
axios({
method: 'post',
url: `${process.env.REACT_APP_LRP_HOST}/api/statements`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.userAccessToken}`,
},
data: JSON.stringify(statement),
})
.then(response => {
console.log('SAVED STATEMENT ID')
console.log(response.data)
})
.catch(error => {
console.log('FAIL TO SEND STATEMENT')
console.log(error.response.data)
})
})
})
}
})
}
}
}
return (
<iframe
title='h5p-embed'
ref={handleContentRef}
src={iframeSrc}
frameBorder='0'
allowFullScreen='allowfullscreen'
/>
)
}
H5pContent.propTypes = {
iframeSrc: PropTypes.string.isRequired,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment