import { JsPsych , JsPsychPlugin , ParameterType , TrialType } from "jspsych" ; const info = < const > { name : "video-button-response" , parameters : { /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */ stimulus : { type : ParameterType . VIDEO , pretty_name : "Video" , default : undefined , array : true , } , /** Array containing the label(s) for the button(s). */ choices : { type : ParameterType . STRING , pretty_name : "Choices" , default : undefined , array : true , } , /** The HTML for creating button. Can create own style. Use the "%choice%" string to indicate where the label from the choices parameter should be inserted. */ button_html : { type : ParameterType . HTML_STRING , pretty_name : "Button HTML" , default : '<button class="jspsych-btn">%choice%</button>' , array : true , } , /** Any content here will be displayed below the buttons. */ prompt : { type : ParameterType . HTML_STRING , pretty_name : "Prompt" , default : null , } , /** The width of the video in pixels. */ width : { type : ParameterType . INT , pretty_name : "Width" , default : "" , } , /** The height of the video display in pixels. */ height : { type : ParameterType . INT , pretty_name : "Height" , default : "" , } , /** If true, the video will begin playing as soon as it has loaded. */ autoplay : { type : ParameterType . BOOL , pretty_name : "Autoplay" , default : true , } , /** If true, the subject will be able to pause the video or move the playback to any point in the video. */ controls : { type : ParameterType . BOOL , pretty_name : "Controls" , default : false , } , /** Time to start the clip. If null (default), video will start at the beginning of the file. */ start : { type : ParameterType . FLOAT , pretty_name : "Start" , default : null , } , /** Time to stop the clip. If null (default), video will stop at the end of the file. */ stop : { type : ParameterType . FLOAT , pretty_name : "Stop" , default : null , } , /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */ rate : { type : ParameterType . FLOAT , pretty_name : "Rate" , default : 1 , } , /** If true, the trial will end immediately after the video finishes playing. */ trial_ends_after_video : { type : ParameterType . BOOL , pretty_name : "End trial after video finishes" , default : false , } , /** How long to show trial before it ends. */ trial_duration : { type : ParameterType . INT , pretty_name : "Trial duration" , default : null , } , /** The vertical margin of the button. */ margin_vertical : { type : ParameterType . STRING , pretty_name : "Margin vertical" , default : "0px" , } , /** The horizontal margin of the button. */ margin_horizontal : { type : ParameterType . STRING , pretty_name : "Margin horizontal" , default : "8px" , } , /** If true, the trial will end when subject makes a response. */ response_ends_trial : { type : ParameterType . BOOL , pretty_name : "Response ends trial" , default : true , } , /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */ response_allowed_while_playing : { type : ParameterType . BOOL , pretty_name : "Response allowed while playing" , default : true , } , } , } ; type Info = typeof info ; author Josh de Leeuw
 *  see https class VideoButtonResponsePlugin implements JsPsychPlugin < Info > { static info = info ; constructor ( private jsPsych ) { } trial ( display_element , trial ) { if ( ! Array . isArray ( trial . stimulus ) ) { throw new Error ( `
        The stimulus property for the video-button-response plugin must be an array
        of files. See https://www.jspsych.org/latest/plugins/video-button-response/#parameters
      ` ) ; } var video_html = "<div>" ; video_html += '<video id="jspsych-video-button-response-stimulus"' ; if ( trial . width ) { video_html += ' width="' + trial . width + '"' ; } if ( trial . height ) { video_html += ' height="' + trial . height + '"' ; } if ( trial . autoplay && trial . start == null ) { video_html += " autoplay " ; } if ( trial . controls ) { video_html += " controls " ; } if ( trial . start !== null ) { video_html += ' style="visibility: hidden;"' ; } video_html += ">" ; var video_preload_blob = this . jsPsych . pluginAPI . getVideoBuffer ( trial . stimulus [ 0 ] ) ; if ( ! video_preload_blob ) { for ( var i = 0 ; i < trial . stimulus . length ; i ++ ) { var file_name = trial . stimulus [ i ] ; if ( file_name . indexOf ( "?" ) > - 1 ) { file_name = file_name . substring ( 0 , file_name . indexOf ( "?" ) ) ; } var type = file_name . substr ( file_name . lastIndexOf ( "." ) + 1 ) ; type = type . toLowerCase ( ) ; if ( type == "mov" ) { console . warn ( "Warning: video-button-response plugin does not reliably support .mov files." ) ; } video_html += '<source src="' + file_name + '" type="video/' + type + '">' ; } } video_html += "</video>" ; video_html += "</div>" ; var buttons = [ ] ; if ( Array . isArray ( trial . button_html ) ) { if ( trial . button_html . length == trial . choices . length ) { buttons = trial . button_html ; } else { console . error ( "Error in video-button-response plugin. The length of the button_html array does not equal the length of the choices array" ) ; } } else { for ( var i = 0 ; i < trial . choices . length ; i ++ ) { buttons . push ( trial . button_html ) ; } } video_html += '<div id="jspsych-video-button-response-btngroup">' ; for ( var i = 0 ; i < trial . choices . length ; i ++ ) { var str = buttons [ i ] . replace ( /%choice%/g , trial . choices [ i ] ) ; video_html += '<div class="jspsych-video-button-response-button" style="cursor: pointer; display: inline-block; margin:' + trial . margin_vertical + " " + trial . margin_horizontal + '" id="jspsych-video-button-response-button-' + i + '" data-choice="' + i + '">' + str + "</div>" ; } video_html += "</div>" ; if ( trial . prompt !== null ) { video_html += trial . prompt ; } display_element . innerHTML = video_html ; var start_time = performance . now ( ) ; var video_element = display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-button-response-stimulus" ) ; if ( video_preload_blob ) { video_element . src = video_preload_blob ; } video_element . onended = ( ) => { if ( trial . trial_ends_after_video ) { end_trial ( ) ; } else if ( ! trial . response_allowed_while_playing ) { enable_buttons ( ) ; } } ; video_element . playbackRate = trial . rate ; if ( trial . start !== null ) { video_element . pause ( ) ; video_element . onseeked = ( ) => { video_element . style . visibility = "visible" ; video_element . muted = false ; if ( trial . autoplay ) { video_element . play ( ) ; } else { video_element . pause ( ) ; } video_element . onseeked = ( ) => { } ; } ; video_element . onplaying = ( ) => { video_element . currentTime = trial . start ; video_element . onplaying = ( ) => { } ; } ; video_element . muted = true ; video_element . play ( ) ; } let stopped = false ; if ( trial . stop !== null ) { video_element . addEventListener ( "timeupdate" , ( e ) => { var currenttime = video_element . currentTime ; if ( currenttime >= trial . stop ) { if ( ! trial . response_allowed_while_playing ) { enable_buttons ( ) ; } video_element . pause ( ) ; if ( trial . trial_ends_after_video && ! stopped ) { stopped = true ; end_trial ( ) ; } } } ) ; } if ( trial . response_allowed_while_playing ) { enable_buttons ( ) ; } else { disable_buttons ( ) ; } var response = { rt : null , button : null , } ; const end_trial = ( ) => { this . jsPsych . pluginAPI . clearAllTimeouts ( ) ; display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-button-response-stimulus" ) . pause ( ) ; display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-button-response-stimulus" ) . onended = ( ) => { } ; var trial_data = { rt : response . rt , stimulus : trial . stimulus , response : response . button , } ; display_element . innerHTML = "" ; this . jsPsych . finishTrial ( trial_data ) ; } ; function after_response ( choice ) { var end_time = performance . now ( ) ; var rt = Math . round ( end_time - start_time ) ; response . button = parseInt ( choice ) ; response . rt = rt ; video_element . className += " responded" ; disable_buttons ( ) ; if ( trial . response_ends_trial ) { end_trial ( ) ; } } function button_response ( e ) { var choice = e . currentTarget . getAttribute ( "data-choice" ) ; after_response ( choice ) ; } function disable_buttons ( ) { var btns = document . querySelectorAll ( ".jspsych-video-button-response-button" ) ; for ( var i = 0 ; i < btns . length ; i ++ ) { var btn_el = btns [ i ] . querySelector ( "button" ) ; if ( btn_el ) { btn_el . disabled = true ; } btns [ i ] . removeEventListener ( "click" , button_response ) ; } } function enable_buttons ( ) { var btns = document . querySelectorAll ( ".jspsych-video-button-response-button" ) ; for ( var i = 0 ; i < btns . length ; i ++ ) { var btn_el = btns [ i ] . querySelector ( "button" ) ; if ( btn_el ) { btn_el . disabled = false ; } btns [ i ] . addEventListener ( "click" , button_response ) ; } } if ( trial . trial_duration !== null ) { this . jsPsych . pluginAPI . setTimeout ( end_trial , trial . trial_duration ) ; } } simulate ( trial , simulation_mode , simulation_options , load_callback ) { if ( simulation_mode == "data-only" ) { load_callback ( ) ; this . simulate_data_only ( trial , simulation_options ) ; } if ( simulation_mode == "visual" ) { this . simulate_visual ( trial , simulation_options , load_callback ) ; } } private create_simulation_data ( trial , simulation_options ) { const default_data = { stimulus : trial . stimulus , rt : this . jsPsych . randomization . sampleExGaussian ( 500 , 50 , 1 / 150 , true ) , response : this . jsPsych . randomization . randomInt ( 0 , trial . choices . length - 1 ) , } ; const data = this . jsPsych . pluginAPI . mergeSimulationData ( default_data , simulation_options ) ; this . jsPsych . pluginAPI . ensureSimulationDataConsistency ( trial , data ) ; return data ; } private simulate_data_only ( trial , simulation_options ) { const data = this . create_simulation_data ( trial , simulation_options ) ; this . jsPsych . finishTrial ( data ) ; } private simulate_visual ( trial , simulation_options , load_callback ) { const data = this . create_simulation_data ( trial , simulation_options ) ; const display_element = this . jsPsych . getDisplayElement ( ) ; this . trial ( display_element , trial ) ; load_callback ( ) ; const video_element = display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-button-response-stimulus" ) ; const respond = ( ) => { if ( data . rt !== null ) { this . jsPsych . pluginAPI . clickTarget ( display_element . querySelector ( `div[data-choice="${ data . response }"] button` ) , data . rt ) ; } } ; if ( ! trial . response_allowed_while_playing ) { video_element . addEventListener ( "ended" , respond ) ; } else { respond ( ) ; } } } export default VideoButtonResponsePlugin ;