Show:

File: app/components/exp-lookit-observation/component.js

import ExpFrameBaseComponent from '../exp-frame-base/component';
import VideoRecord from '../../mixins/video-record';
import layout from './template';
import Em from 'ember';
import { observer } from '@ember/object';

let {
    $
} = Em;

/**
 * @module exp-player
 * @submodule frames
 */

/**
 * A frame to collect a video observation with the participant's help. By default the
 * webcam is displayed to the participant and they can choose when to start, pause, and
 * resume recording. The duration of an individual recording can optionally be limited
 * and/or recording can be started automatically. This is intended for cases where we
 * want the parent to perform some test or behavior with the child, rather than
 * presenting stimuli ourselves. E.g., you might give instructions to conduct a structured
 * interview and allow the parent to control recording.
 *
 * Each element of the 'blocks' parameter is rendered using {{#crossLink "Exp-text-block"}}{{/crossLink}}.
 *
 ```
    "frames": {
        "observation": {
            "kind": "exp-lookit-observation",
            "blocks": [
                {
                    "title": "Time to do the joke!",
                    "listblocks": [
                        {
                            "text": "Rip the paper"
                        },
                        {
                            "text": "Wait ten seconds"
                        }
                    ]
                }
            ],
            "hideWebcam": true,
            "hideControls": false,
            "recordSegmentLength": 10,
            "startRecordingAutomatically": false,
            "nextButtonText": "move on",
            "showPreviousButton": false
        }
    }
```
 * @class Exp-lookit-observation
 * @extends Exp-frame-base
 * @extends Video-record
 */

export default ExpFrameBaseComponent.extend(VideoRecord, {
    type: 'exp-lookit-observation',
    layout: layout,

    recordingTimer: null,
    progressTimer: null,
    okayToProceedTimer: null,

    timerStart: null,
    hasStartedRecording: false,
    recordingStarted: false,
    toggling: false,
    hidden: false,
    recorderElement: '#recorder',

    frameSchemaProperties: {
        /**
         * Array of blocks for {{#crossLink "Exp-text-block"}}{{/crossLink}}, specifying text/images of instructions to display
         *
         * @property {Object[]} blocks
         *   @param {String} title Title of this section
         *   @param {String} text Paragraph text of this section
         *   @param {Object[]} listblocks Object specifying bulleted points for this section. Each object is of the form:
         *   {text: 'text of bullet point', image: {src: 'url', alt: 'alt-text'}}. Images are optional.
         */
        blocks: {
            type: 'array',
            items: {
                type: 'object',
                properties: {
                    title: {
                        type: 'string'
                    },
                    text: {
                        type: 'string'
                    },
                    listblocks: {
                        type: 'array',
                        items: {
                            type: 'object',
                            properties: {
                                text: {
                                    type: 'string'
                                },
                                image: {
                                    type: 'object',
                                    properties: {
                                        src: {
                                            type: 'string'
                                        },
                                        alt: {
                                            type: 'string'
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            default: []
        },
        /**
         * Number of seconds to record for before automatically pausing. Use
         * 0 for no limit.
         *
         * @property {String} recordSegmentLength
         * @default 300
         */
        recordSegmentLength: {
            type: 'number',
            default: 300
        },
        /**
         * Whether to automatically begin recording upon frame load
         *
         * @property {Boolean} startRecordingAutomatically
         * @default false
         */
        startRecordingAutomatically: {
            type: 'boolean',
            default: false
        },
        /**
         * Whether a recording must be made to proceed to next frame. 'Next' button
         * will be disabled until recording is made if so. 0 to not require recording;
         * any positive number to require that many seconds of recording
         *
         * @property {Boolean} recordingRequired
         * @default false
         */
        recordingRequired: {
            type: 'number',
            default: 0
        },
        /**
         * Whether to hide video recording controls (only use with startRecordingAutomatically)
         *
         * @property {Boolean} hideControls
         * @default false
         */
        hideControls: {
            type: 'boolean',
            default: false
        },
        /**
         * Whether to hide webcam view when frame loads (participant will still be able to show manually)
         *
         * @property {Boolean} hideWebcam
         * @default false
         */
        hideWebcam: {
            type: 'boolean',
            default: false
        },
        /**
         * Text to display on the 'next frame' button
         *
         * @property {String} nextButtonText
         * @default 'Next'
         */
        nextButtonText: {
            type: 'string',
            default: 'Next'
        },
        /**
         * Whether to show a 'previous' button
         *
         * @property {Boolean} showPreviousButton
         * @default true
         */
        showPreviousButton: {
            type: 'boolean',
            default: true
        }
    },

    meta: {
        data: {
            type: 'object',
            properties: {
                videoId: {
                    type: 'string'

                },
                videoList: {
                    type: 'list'
                }
            },
            required: ['videoId']
        }
    },

    // Override to deal with whether or not recording is starting automatically
    whenPossibleToRecord: observer('recorder.hasCamAccess', 'recorderReady', function() {

        if (this.get('recorder.hasCamAccess') && this.get('recorderReady')) {
            if (this.get('startRecordingAutomatically')) {
                this.send('record');
            } else {
                $('#recordButton').show();
                $('#recordingText').text('Not recording yet');
            }

            if (this.get('hideWebcam')) {
                $('#webcamToggleButton').html('Show');
                $('#hiddenWebcamMessage').show();
                $(this.get('recorderElement') + ' div').addClass('exp-lookit-observation-hidevideo');
                this.set('hidden', true);
                /**
                 * Webcam display hidden from participant
                 *
                 * @event hideWebcam
                 */
                this.send('setTimeEvent', 'hideWebcam');
            }
        }


    }),

    didInsertElement() { // initial state of all buttons/text
        $('#hiddenWebcamMessage').hide();
        $('#recordButton').hide();
        $('#pauseButton').hide();
        $('#recordingIndicator').hide();
        $('#recordingText').text('');
        $('#recordButtonText').text('Record');
        if (this.get('recordingRequired')) {
            $('#nextbutton').prop('disabled', true);
            $('#nextbutton').text('Recording required to continue');
        }
        this._super(...arguments);
    },

    enableNext() {
        $('#nextbutton').prop('disabled', false);
        $('#nextbutton').text(this.get('nextButtonText'));
    },

    actions: {
        record() {

            this.startRecorder(); // TODO: use then

            var _this = this;
            if (this.get('recordSegmentLength')) { // no timer if 0
                window.clearTimeout(this.get('recordingTimer')); // as a precaution in case still running
                window.clearInterval(this.get('progressTimer'));
                window.clearTimeout(this.get('okayToProceedTimer'));
                this.set('timerStart', new Date().getTime());
                this.set('recordingTimer', window.setTimeout(function() {
                    /**
                     * Video recording automatically paused upon reaching time limit
                     *
                     * @event recorderTimeout
                     */
                    _this.send('setTimeEvent', 'recorderTimeout');
                    _this.send('pause');
                }, _this.get('recordSegmentLength') * 1000));
                this.set('progressTimer', window.setInterval(function() {
                    var prctDone =  (_this.get('recordSegmentLength') * 1000 - (new Date().getTime() - _this.get('timerStart'))) / (_this.get('recordSegmentLength') * 10);
                    $('.progress-bar').css('width', prctDone + '%');
                }, 100));
                if (this.get('recordingRequired')) {
                    this.set('okayToProceedTimer', window.setTimeout(function() {
                        _this.enableNext();
                    }, 1000 * this.get('recordingRequired')));
                }
            }
            $('#pauseButton').show();
            $('#recordButton').hide();
            $('#recordingIndicator').show();
            $('#recordingText').text('Recording...');
            $('#recordButtonText').text('Resume');
        },

        proceed() { // make sure 'next' fires while still on this frame
            window.clearTimeout(this.get('recordingTimer')); // no need for current timer
            window.clearTimeout(this.get('okayToProceedTimer'));
            window.clearInterval(this.get('progressTimer'));
            this.stopRecorder().finally(() => {
                this.destroyRecorder();
                this.send('next');
            });
        },
        pause() {
            var _this = this;
            $('#recordingText').text('Stopping and uploading...');
            $('#pauseButton').hide();
            window.clearTimeout(_this.get('recordingTimer')); // no need for current timer
            window.clearTimeout(this.get('okayToProceedTimer'));
            window.clearInterval(_this.get('progressTimer'));
            $('.progress-bar').css('width', '100%');
            $('#recordingIndicator').hide();
            this.stopRecorder().finally(() => {
                $('#recordButton').show();
                $('#recordingText').text('Paused');
                _this.destroyRecorder();
                _this.setupRecorder(_this.$(_this.get('recorderElement')));
            });
        },
        toggleWebcamButton() {
            if (!this.toggling) {
                this.set('toggling', true);
                if (!this.get('hidden')) {
                    $('#webcamToggleButton').html('Show');
                    $('#hiddenWebcamMessage').show();
                    $(this.get('recorderElement') + ' div').addClass('exp-lookit-observation-hidevideo');
                    this.set('hidden', true);
                    /**
                     * Webcam display hidden from participant
                     *
                     * @event hideWebcam
                     */
                    this.send('setTimeEvent', 'hideWebcam');
                } else {
                    $('#webcamToggleButton').html('Hide');
                    $('#hiddenWebcamMessage').hide();
                    $(this.get('recorderElement') + ' div').removeClass('exp-lookit-observation-hidevideo');
                    this.set('hidden', false);
                    /**
                     * Webcam display shown to participant
                     *
                     * @event showWebcam
                     */
                    this.send('setTimeEvent', 'showWebcam');
                }
                this.set('toggling', false);
            }
        }
    }
});