import { JsPsych , JsPsychPlugin , ParameterType , TrialType } from "jspsych" ; const info = < const > { name : "video-slider-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 , } , /** Any content here will be displayed below the stimulus. */ 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 , } , /** Sets the minimum value of the slider. */ min : { type : ParameterType . INT , pretty_name : "Min slider" , default : 0 , } , /** Sets the maximum value of the slider. */ max : { type : ParameterType . INT , pretty_name : "Max slider" , default : 100 , } , /** Sets the starting value of the slider. */ slider_start : { type : ParameterType . INT , pretty_name : "Slider starting value" , default : 50 , } , /** Sets the step of the slider. */ step : { type : ParameterType . INT , pretty_name : "Step" , default : 1 , } , /** Array containing the labels for the slider. Labels will be displayed at equidistant locations along the slider. */ labels : { type : ParameterType . HTML_STRING , pretty_name : "Labels" , default : [ ] , array : true , } , /** Width of the slider in pixels. */ slider_width : { type : ParameterType . INT , pretty_name : "Slider width" , default : null , } , /** Label of the button to advance. */ button_label : { type : ParameterType . STRING , pretty_name : "Button label" , default : "Continue" , } , /** If true, the participant will have to move the slider before continuing. */ require_movement : { type : ParameterType . BOOL , pretty_name : "Require movement" , default : false , } , /** 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 , } , /** 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 VideoSliderResponsePlugin 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-slider-response plugin must be an array
        of files. See https://www.jspsych.org/latest/plugins/video-slider-response/#parameters
      ` ) ; } var half_thumb_width = 7.5 ; var video_html = '<video id="jspsych-video-slider-response-stimulus-video"' ; 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-slider-response plugin does not reliably support .mov files." ) ; } video_html += '<source src="' + file_name + '" type="video/' + type + '">' ; } } video_html += "</video>" ; var html = '<div id="jspsych-video-slider-response-wrapper" style="margin: 100px 0px;">' ; html += '<div id="jspsych-video-slider-response-stimulus">' + video_html + "</div>" ; html += '<div class="jspsych-video-slider-response-container" style="position:relative; margin: 0 auto 3em auto; width:' ; if ( trial . slider_width !== null ) { html += trial . slider_width + "px;" ; } else { html += "auto;" ; } html += '">' ; html += '<input type="range" class="jspsych-slider" value="' + trial . slider_start + '" min="' + trial . min + '" max="' + trial . max + '" step="' + trial . step + '" id="jspsych-video-slider-response-response"' ; if ( ! trial . response_allowed_while_playing ) { html += " disabled" ; } html += "></input><div>" ; for ( var j = 0 ; j < trial . labels . length ; j ++ ) { var label_width_perc = 100 / ( trial . labels . length - 1 ) ; var percent_of_range = j * ( 100 / ( trial . labels . length - 1 ) ) ; var percent_dist_from_center = ( ( percent_of_range - 50 ) / 50 ) * 100 ; var offset = ( percent_dist_from_center * half_thumb_width ) / 100 ; html += '<div style="border: 1px solid transparent; display: inline-block; position: absolute; ' + "left:calc(" + percent_of_range + "% - (" + label_width_perc + "% / 2) - " + offset + "px); text-align: center; width: " + label_width_perc + '%;">' ; html += '<span style="text-align: center; font-size: 80%;">' + trial . labels [ j ] + "</span>" ; html += "</div>" ; } html += "</div>" ; html += "</div>" ; html += "</div>" ; if ( trial . prompt !== null ) { html += "<div>" + trial . prompt + "</div>" ; } var next_disabled_attribute = "" ; if ( trial . require_movement || ! trial . response_allowed_while_playing ) { next_disabled_attribute = "disabled" ; } html += '<button id="jspsych-video-slider-response-next" class="jspsych-btn" ' + next_disabled_attribute + ">" + trial . button_label + "</button>" ; display_element . innerHTML = html ; var video_element = display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-slider-response-stimulus-video" ) ; 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_slider ( ) ; } } ; 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 ) { video_element . pause ( ) ; if ( trial . trial_ends_after_video && ! stopped ) { stopped = true ; end_trial ( ) ; } if ( ! trial . response_allowed_while_playing ) { enable_slider ( ) ; } } } ) ; } if ( trial . require_movement ) { const enable_button = ( ) => { display_element . querySelector < HTMLInputElement > ( "#jspsych-video-slider-response-next" ) . disabled = false ; } ; display_element . querySelector ( "#jspsych-video-slider-response-response" ) . addEventListener ( "mousedown" , enable_button ) ; display_element . querySelector ( "#jspsych-video-slider-response-response" ) . addEventListener ( "touchstart" , enable_button ) ; display_element . querySelector ( "#jspsych-video-slider-response-response" ) . addEventListener ( "change" , enable_button ) ; } var startTime = performance . now ( ) ; var response = { rt : null , response : null , } ; const end_trial = ( ) => { this . jsPsych . pluginAPI . clearAllTimeouts ( ) ; display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-slider-response-stimulus-video" ) . pause ( ) ; display_element . querySelector < HTMLVideoElement > ( "#jspsych-video-slider-response-stimulus-video" ) . onended = ( ) => { } ; var trial_data = { rt : response . rt , stimulus : trial . stimulus , start : trial . start , slider_start : trial . slider_start , response : response . response , } ; display_element . innerHTML = "" ; this . jsPsych . finishTrial ( trial_data ) ; } ; display_element . querySelector ( "#jspsych-video-slider-response-next" ) . addEventListener ( "click" , ( ) => { var endTime = performance . now ( ) ; response . rt = Math . round ( endTime - startTime ) ; response . response = display_element . querySelector < HTMLInputElement > ( "#jspsych-video-slider-response-response" ) . valueAsNumber ; if ( trial . response_ends_trial ) { end_trial ( ) ; } else { display_element . querySelector < HTMLButtonElement > ( "#jspsych-video-slider-response-next" ) . disabled = true ; } } ) ; function enable_slider ( ) { ( document . querySelector ( "#jspsych-video-slider-response-response" ) as HTMLInputElement ) . disabled = false ; if ( ! trial . require_movement ) { ( document . querySelector ( "#jspsych-video-slider-response-next" ) as HTMLInputElement ) . disabled = false ; } } 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 , slider_start : trial . slider_start , response : this . jsPsych . randomization . randomInt ( trial . min , trial . max ) , rt : this . jsPsych . randomization . sampleExGaussian ( 500 , 50 , 1 / 150 , true ) , start : trial . start , } ; 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 ) { const el = display_element . querySelector < HTMLInputElement > ( "input[type='range']" ) ; setTimeout ( ( ) => { this . jsPsych . pluginAPI . clickTarget ( el ) ; el . valueAsNumber = data . response ; } , data . rt / 2 ) ; this . jsPsych . pluginAPI . clickTarget ( display_element . querySelector ( "button" ) , data . rt ) ; } } ; if ( ! trial . response_allowed_while_playing ) { video_element . addEventListener ( "ended" , respond ) ; } else { respond ( ) ; } } } export default VideoSliderResponsePlugin ;