Introduction
The Fieldtrip Project, an NIH-sponsored website, was created to engage adolescents with educational content by inviting them to watch short films and participate in an online community. Researchers aimed to spur meaningful discussions and encourage adolescents to share their thoughts on education and identity development.
A key component of this online environment was the Flash-based video player, which enabled users to watch short films and log their activity. However, as Flash support gradually phased out across modern browsers, the project faced the risk of losing its most critical features—particularly video playback and research logging.
Original Flash ActionScript
import flash.geom.ColorTransform;
import flash.utils.*;
import fl.video.*;
import flash.text.*;
import flash.external.ExternalInterface;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
stop();
var videoDown:Boolean = false;
var oldVolume:Number = 1;
var started:Boolean = false;
var muted:Boolean = false;
var videoTimer:Timer = new Timer(250, 1);
var mouseTimer:Timer = new Timer(1750, 1);
var dragVolume:Boolean = false;
var volumeUp:Boolean = false;
var percentage:Number;
var volumePercent:Number = 1;
var idle:Boolean = false;
var xmlNavData:XML;
var filename:String;
init();
function init()
{
loading.x = stage.stageWidth/2 - loading.width/2;
loading.y = stage.stageHeight/2 - loading.height/2;
video.videoButtons.hitArea.width = stage.stageWidth;
video.videoButtons.hitArea.height = stage.stageHeight;
video.videoButtons.timeDisplay.mouseEnabled=false;
video.videoButtons.timeDisplay.blendMode = BlendMode.LAYER;
video.videoButtons.volumeButton.slider.sliderBG.mouseEnabled=false;
video.videoButtons.volumeButton.slider.bg.alpha = .5;
video.videoButtons.videoTimeline.alpha = .5;
video.videoButtons.volumeButton.slider.bg.y = 0;
video.videoButtons.shine.mouseEnabled=false;
video.videoButtons.volumeButton.mute.icon.gotoAndStop(101);
video.videoButtons.volumeButton.mute.icon.mouseEnabled=false;
video.videoButtons.videoTimeline.timelineLines.mouseEnabled=false;
video.videoButtons.videoTimeline.y = stage.stageHeight - 40;
video.videoButtons.volumeButton.x = stage.stageWidth - 60;
video.videoButtons.volumeButton.y = stage.stageHeight - 40;
video.videoButtons.videoTimeline.width = stage.stageWidth;
video.videoButtons.videoTimeline.timelineLines.width = stage.stageWidth - 60;
video.videoButtons.playPauseButton.x = stage.stageWidth/2;
video.videoButtons.playPauseButton.y = stage.stageHeight/2;
video.flvPlayer.height = stage.stageHeight;
video.flvPlayer.width = stage.stageWidth;
video.flvPlayer.scaleMode = VideoScaleMode.EXACT_FIT;
video.videoButtons.timeDisplay.y = stage.stageHeight - 20 - (video.videoButtons.timeDisplay.height/2);
video.videoButtons.timeDisplay.x = stage.stageWidth - 60 - video.videoButtons.timeDisplay.width;
mouseTimeDisplay.y = stage.stageHeight;
GetNavigateTo();
}
function loadVideo()
{
video.alpha = 0;
video.flvPlayer.visible = false;
video.videoButtons.playPauseButton.gotoAndStop(1);
video.videoButtons.videoTimeline.played.width = 0;
video.videoButtons.videoTimeline.loaded.width = 0;
var loader:Loader = new Loader();
video.flvPlayer.source = "videos/" + filename + ".flv";
loader.load(new URLRequest("videos/" + filename + ".png"));
video.poster.addChild(loader);
video.poster.alpha = 1;
video.flvPlayer.addEventListener(VideoEvent.COMPLETE, videoComplete);
addEventListener(Event.ENTER_FRAME, videoUpdater);
video.videoButtons.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER));
video.flvPlayer.addEventListener(VideoEvent.READY, videoReady);
}
function videoReady(e:VideoEvent)
{
enableVideoButtons();
startVideo()
}
function startVideo()
{
video.flvPlayer.removeEventListener(VideoEvent.READY, videoReady);
}
function unloadVideo()
{
mouseTimeDisplay.visible=false;
video.flvPlayer.pause();
removeEventListener(Event.ENTER_FRAME, videoUpdater);
disableVideoButtons();
video.flvPlayer.removeEventListener(VideoEvent.COMPLETE, videoComplete);
}
function timelineClick(e:MouseEvent)
{
video.flvPlayer.seek((mouseX) / (stage.stageWidth - 60) * video.flvPlayer.totalTime);
video.flvPlayer.addEventListener(VideoEvent.SEEKED, loadingOff);
}
function loadingOff(e:VideoEvent)
{
if(video.poster.alpha > 0)
{
video.flvPlayer.visible=true;
}
TweenMax.to(video.videoButtons.loading, .25, {alpha:0});
video.flvPlayer.removeEventListener(VideoEvent.SEEKED, loadingOff);
}
function videoComplete(e:VideoEvent)
{
video.flvPlayer.pause();
videoOver();
video.videoButtons.playPauseButton.gotoAndStop(1);
videoTimer.removeEventListener(TimerEvent.TIMER, hideVideoControls);
mouseTimer.removeEventListener(TimerEvent.TIMER, triggerMouseTimer);
}
function rewind()
{
video.flvPlayer.seek(0);
}
function playPause(e:MouseEvent)
{
if(mouseY < video.videoButtons.videoTimeline.y && video.alpha > .5)
{
video.flvPlayer.visible=true;
if(video.flvPlayer.state == "playing")
{
video.videoButtons.playPauseButton.gotoAndStop(1);
video.flvPlayer.pause();
videoTimer.removeEventListener(TimerEvent.TIMER, hideVideoControls);
mouseTimer.removeEventListener(TimerEvent.TIMER, triggerMouseTimer);
}
else if(video.flvPlayer.state == "paused" || video.flvPlayer.state == "stopped" || video.flvPlayer.state == "seeking")
{
video.videoButtons.playPauseButton.gotoAndStop(2);
video.flvPlayer.play();
videoTimer.addEventListener(TimerEvent.TIMER, hideVideoControls);
mouseTimer.addEventListener(TimerEvent.TIMER, triggerMouseTimer);
addEventListener(MouseEvent.MOUSE_MOVE, resetMouseTimer);
mouseTimer.reset();
mouseTimer.start();
}
}
}
function volumeOver(e:MouseEvent)
{
if(!dragVolume && !videoDown)
{
volumeUp=true;
video.videoButtons.removeEventListener(MouseEvent.CLICK, playPause);
video.videoButtons.volumeButton.slider.bar.addEventListener(MouseEvent.MOUSE_DOWN, dragVolumeBar);
}
}
function volumeOut(e:MouseEvent)
{
if(!dragVolume)
{
volumeUp=false;
video.videoButtons.addEventListener(MouseEvent.CLICK, playPause);
}
}
function volumeClick(e:MouseEvent)
{
var target = (mouseY - stage.stageHeight+140) * .95
if(target < 15)
target = 15;
if(target >= 85)
{
target = 85;
muted=true;
}
else
{
muted=false;
}
}
function setOldVolume()
{
oldVolume = video.flvPlayer.volume;
}
function updateVolumeBar()
{
video.videoButtons.volumeButton.slider.bar.y = video.videoButtons.volumeButton.slider.sliderBG.y + (Math.abs(1-volumePercent) * video.videoButtons.volumeButton.slider.sliderBG.height);
}
function mute(e:MouseEvent)
{
if(!muted)
{
muted = true;
}
else
{
muted = false;
}
}
function updateVolume()
{
volumePercent = Math.abs(1-(video.videoButtons.volumeButton.slider.bar.y - video.videoButtons.volumeButton.slider.sliderBG.y) / video.videoButtons.volumeButton.slider.sliderBG.height);
video.flvPlayer.volume = volumePercent;
video.videoButtons.volumeButton.mute.icon.gotoAndStop(Math.floor(volumePercent*100-1));
}
function dragVolumeBar(e:MouseEvent)
{
dragVolume = true;
video.videoButtons.volumeButton.slider.bg.removeEventListener(MouseEvent.MOUSE_OUT, volumeOut);
var rect:Rectangle = new Rectangle(video.videoButtons.volumeButton.slider.sliderBG.x, video.videoButtons.volumeButton.slider.sliderBG.y, 0, video.videoButtons.volumeButton.slider.sliderBG.height);
video.videoButtons.volumeButton.slider.bar.startDrag(false, rect);
addEventListener(MouseEvent.MOUSE_UP, stopDragVolumeBar);
addEventListener(MouseEvent.MOUSE_MOVE, draggingVolumeBar);
}
function stopDragVolumeBar(e:MouseEvent)
{
dragVolume=false;
video.videoButtons.volumeButton.slider.bar.stopDrag();
video.videoButtons.volumeButton.addEventListener(MouseEvent.MOUSE_OUT, volumeOut);
removeEventListener(MouseEvent.MOUSE_UP, stopDragVolumeBar);
removeEventListener(MouseEvent.MOUSE_MOVE, draggingVolumeBar);
video.videoButtons.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT));
if(volumePercent > 0)
{
setOldVolume()
muted=false;
}
else
muted=true;
}
function draggingVolumeBar(e:MouseEvent)
{
volumePercent = Math.abs(1-((video.videoButtons.volumeButton.slider.bar.y - video.videoButtons.volumeButton.slider.sliderBG.y) / video.videoButtons.volumeButton.slider.sliderBG.height));
video.flvPlayer.volume = volumePercent;
video.videoButtons.volumeButton.mute.icon.gotoAndStop(Math.floor(volumePercent*100-1));
}
function triggerMouseTimer(e:TimerEvent)
{
idle=true;
videoTimer.reset();
videoTimer.start();
}
function videoOver()
{
Mouse.show();
}
function videoOut()
{
videoTimer.reset();
videoTimer.start();
}
function videoDownFalse()
{
videoDown=false;
}
function hideVideoControls(e:TimerEvent)
{
if(video.flvPlayer.state == "playing" && !volumeUp)
{
videoDown=true;
mouseTimer.removeEventListener(TimerEvent.TIMER, triggerMouseTimer);
Mouse.hide();
}
}
function videoUpdater(e:Event)
{
var total:Number = video.flvPlayer.totalTime;
var playhead:Number = video.flvPlayer.playheadTime;
//TOTAL TIME
var Ttime:Number = Math.floor(Number(video.flvPlayer.totalTime));
var minutes:Number = Math.floor(Ttime/60);
var seconds = Math.floor(Ttime%60);
if (seconds<10)
seconds = ("0"+seconds);
//CURRENT PLAYING TIME
var Ttime2:Number = Math.floor(Number(video.flvPlayer.playheadTime));
var minutes2:Number = Math.floor(Ttime2/60);
var seconds2 = Math.floor(Ttime2%60);
if (seconds2<10)
seconds2 = ("0"+seconds2);
if(mouseY > stage.stageHeight - 40 && mouseY < stage.stageHeight && mouseX < stage.stageWidth - 60 && !dragVolume && video.videoButtons.videoTimeline.y == stage.stageHeight - 40)
{
if(!mouseTimeDisplay.visible)
{
mouseTimeDisplay.visible=true;
}
var Ttime3:Number;
Ttime3 = Math.floor((mouseX) / (stage.stageWidth - 60) * video.flvPlayer.totalTime);
var minutes3:Number = Math.floor(Ttime3/60);
var seconds3 = Math.floor(Ttime3%60);
if (seconds3<10)
seconds3 = ("0"+seconds3);
mouseTimeDisplay.display.text = minutes3 + ":" + seconds3;
mouseTimeDisplay.display.autoSize = "center";
mouseTimeDisplay.scaleX = 1;
mouseTimeDisplay.scaleY = 1;
mouseTimeDisplay.x = mouseX;
if(mouseTimeDisplay.x -(mouseTimeDisplay.display.width/2) <= 0)
mouseTimeDisplay.display.x = -(mouseTimeDisplay.display.width/2) + (video.x - mouseTimeDisplay.x + mouseTimeDisplay.display.width/2);
else
mouseTimeDisplay.display.x = -(mouseTimeDisplay.display.width/2);
}
else
mouseTimeDisplay.visible=false;
video.videoButtons.videoTimeline.loaded.width = video.flvPlayer.bytesLoaded / video.flvPlayer.bytesTotal * (stage.stageWidth -60);
video.videoButtons.videoTimeline.played.width = video.flvPlayer.playheadTime / video.flvPlayer.totalTime * (stage.stageWidth -60);
video.videoButtons.timeDisplay.text = minutes2 + ":" + seconds2 + " / " + minutes + ":" + seconds;
if(mouseX < video.x || mouseX > video.x + video.width || mouseY < 0 || mouseY > video.height)
{
if(video.flvPlayer.state == "playing" && !dragVolume && !videoTimer.running && !TweenMax.isTweening(video.videoButtons.playPauseButton))
{
videoOut();
}
}
else
{
if(!idle)
videoOver();
}
}
function resetMouseTimer(e:MouseEvent)
{
idle=false;
mouseTimer.addEventListener(TimerEvent.TIMER, triggerMouseTimer);
mouseTimer.reset();
mouseTimer.start();
}
function enableVideoButtons()
{
video.videoButtons.addEventListener(MouseEvent.CLICK, playPause);
video.videoButtons.volumeButton.addEventListener(MouseEvent.MOUSE_OVER, volumeOver);
video.videoButtons.volumeButton.addEventListener(MouseEvent.MOUSE_OUT, volumeOut);
video.videoButtons.volumeButton.mute.addEventListener(MouseEvent.CLICK, mute);
video.videoButtons.volumeButton.slider.bar.addEventListener(MouseEvent.MOUSE_OVER, volumeOver);
video.videoButtons.volumeButton.slider.bar.addEventListener(MouseEvent.MOUSE_OUT, volumeOut);
video.videoButtons.volumeButton.slider.bg.addEventListener(MouseEvent.CLICK, volumeClick);
video.videoButtons.videoTimeline.addEventListener(MouseEvent.CLICK, timelineClick);
video.videoButtons.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT));
}
function disableVideoButtons()
{
video.videoButtons.removeEventListener(MouseEvent.CLICK, playPause);
video.videoButtons.volumeButton.removeEventListener(MouseEvent.MOUSE_OVER, volumeOver);
video.videoButtons.volumeButton.removeEventListener(MouseEvent.MOUSE_OUT, volumeOut);
video.videoButtons.volumeButton.mute.removeEventListener(MouseEvent.CLICK, mute);
video.videoButtons.volumeButton.slider.bar.removeEventListener(MouseEvent.MOUSE_OVER, volumeOver);
video.videoButtons.volumeButton.slider.bar.removeEventListener(MouseEvent.MOUSE_OUT, volumeOut);
video.videoButtons.volumeButton.slider.bg.removeEventListener(MouseEvent.CLICK, volumeClick);
video.videoButtons.videoTimeline.removeEventListener(MouseEvent.CLICK, timelineClick);
}
function GetNavigateTo():void
{
var xmlURLReqGetNavigate:URLRequest = new URLRequest("Filename.xml");
xmlURLReqGetNavigate.method = URLRequestMethod.POST;
var xmlGetNav:URLLoader = new URLLoader();
xmlGetNav.addEventListener(Event.COMPLETE, onCompleteGetNavigateTo, false, 0, true);
xmlGetNav.addEventListener(IOErrorEvent.IO_ERROR, noNavigateTo, false, 0, true);
xmlGetNav.load(xmlURLReqGetNavigate); //get navigate to
}
function onCompleteGetNavigateTo(e:Event):void
{
try
{
// Sample XML structure:
//
//
// test
//
//
xmlNavData = new XML(e.target.data);
var userXML = xmlNavData.navigate[0];
if(userXML.navigateTo.toString() != "")
filename = userXML.navigateTo.text();
else
filename = "test";
}
catch (err:TypeError)
{
filename = "test"
}
removeEventListener(Event.COMPLETE, onCompleteGetNavigateTo);
loadVideo();
}
function noNavigateTo(e:Event):void //So we can test offline
{
removeEventListener(Event.COMPLETE, onCompleteGetNavigateTo);
removeEventListener(IOErrorEvent.IO_ERROR, noNavigateTo);
filename = "test";
loadVideo();
}
The Refactoring Journey
With the gradual obsolescence of Flash, there was the need for a modern approach to preserve video playback and data logging. This strategy involved converting the Flash ActionScript solution into a fully functional HTML5/JavaScript player— capable of maintaining the same interactions while improving security and compatibility.
xFactor with imported code
xFactor is Devis’ airgapped, AI-driven solution for full-stack development, system innovation, and secure refactoring of legacy applications. This platform leverages advanced AI technologies and custom Large Language Models (LLMs) to streamline every stage of development—from initial architecture and design to implementation and deployment—enabling organizations to build scalable, secure, and future-ready systems. By working on airgapped networks and employing advanced security measures, xFactor preserves data integrity and operational continuity even in sensitive environments. Development InfoStructure, LLC (Devis) proudly introduces xFactor as a state-of-the-art platform designed to transform Full-Stack Development and System Creation. It automates code conversion between languages to ensure accurate, context-aware translations and supports secure legacy application lift-and-shift operations, particularly on SIPRNet. Leveraging an airgapped network of LLMs, including custom Precision Language Models (PLMs), xFactor employs genetic algorithms and ensemble techniques to evaluate and refactor extensive codebases with improved precision, enhancing its effectiveness in code assessment and transformation.
Initial refactoring prompt
xFactor was used to modernize this Flash code. After importing the code, an instruction to transform the Flash code into an HTML5-based video player. This prompt established that the resulting player must maintain the same functionality—especially its logging capability—and simply replace the video source with a modern URL.
“Refactor the provided Flash code into an HTML5 video player page using JavaScript and CSS. Ensure the refactored code maintains the same functionality and behavior as the original Flash code, including the logging functionality which should be displayed below the video player. Replace the existing video source with the video from the URL - https://genitive.ai/wp-content/uploads/2025/01/xFactor-Refactoring.mp4”
Initial Code Generation
xFactor’s initial code generation produced a straightforward HTML5 structure for the video player, providing the essential skeleton for playback, controls, and logging. Below is the first draft of the refactored code that replaced the Flash interface.
View Initial Code Generation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Player</title>
<style>
#video-container {
width: 640px;
height: 480px;
border: 1px solid black;
position: relative;
}
#video-controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
box-sizing: border-box;
}
#logging-display {
position: absolute;
bottom: -150px;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
box-sizing: border-box;
overflow-y: scroll;
height: 140px;
}
</style>
</head>
<body>
<div id="video-container">
<video id="video" width="640" height="480"></video>
<div id="video-controls">
<button id="play-pause-button">Play/Pause</button>
<input id="volume-slider" type="range" min="0" max="1" value="1" step="0.01">
<span id="time-display">00:00 / 00:00</span>
</div>
<div id="logging-display"></div>
</div>
<script>
const video = document.getElementById('video');
const playPauseButton = document.getElementById('play-pause-button');
const volumeSlider = document.getElementById('volume-slider');
const timeDisplay = document.getElementById('time-display');
const loggingDisplay = document.getElementById('logging-display');
let videoPlaying = false;
let volumeLevel = 1;
let muted = false;
let idle = false;
let videoDuration = 0;
let videoCurrentTime = 0;
// Set the video source URL
video.src = 'https://genitive.ai/wp-content/uploads/2025/01/xFactor-Refactoring.mp4';
// On canplay, get the video's duration and start playing automatically
video.addEventListener('canplay', () => {
videoDuration = video.duration;
video.play();
videoPlaying = true;
});
// Update the current time display during playback
video.addEventListener('timeupdate', () => {
videoCurrentTime = video.currentTime;
updateTimeDisplay();
});
// When the video ends, reset states
video.addEventListener('ended', () => {
videoPlaying = false;
playPauseButton.textContent = 'Play';
});
// Handle the Play/Pause button clicks
playPauseButton.addEventListener('click', () => {
if (videoPlaying) {
video.pause();
videoPlaying = false;
playPauseButton.textContent = 'Play';
} else {
video.play();
videoPlaying = true;
playPauseButton.textContent = 'Pause';
}
});
// Listen for volume slider changes
volumeSlider.addEventListener('input', () => {
volumeLevel = volumeSlider.value;
video.volume = volumeLevel;
muted = (volumeLevel === '0');
});
// Format the time display
function updateTimeDisplay() {
const minutes = Math.floor(videoCurrentTime / 60);
const seconds = Math.floor(videoCurrentTime % 60);
const durationMinutes = Math.floor(videoDuration / 60);
const durationSeconds = Math.floor(videoDuration % 60);
timeDisplay.textContent = `\{padZero\(minutes\)\}\:{padZero(seconds)} / \{padZero\(durationMinutes\)\}\:{padZero(durationSeconds)}`;
}
function padZero(number) {
return (number < 10 ? '0' : '') + number;
}
// Logging functionality
function logMessage(message) {
const logEntry = document.createElement('div');
logEntry.textContent = message;
loggingDisplay.appendChild(logEntry);
}
// Additional event listeners for logging
video.addEventListener('play', () => {
logMessage('Video started playing');
});
video.addEventListener('pause', () => {
logMessage('Video paused');
});
video.addEventListener('volumechange', () => {
logMessage(`Volume changed to ${volumeLevel}`);
});
video.addEventListener('ended', () => {
logMessage('Video ended');
});
</script>
</body>
</html>
Refactored & Commented Code
Next, xFactor applied its enhance code feature to thoroughly comment the initial code, clarifying each step’s purpose and ensuring maintainability. Below is the fully Refactored & Commented Code that preserves logging functionality and introduces basic controls in a more robust, modern structure.
View Refactored & Commented Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Refactored Video Player</title>
<style>
/* Container for the video */
#video-container {
width: 640px;
height: 480px;
border: 1px solid black;
position: relative;
}
/* Video controls styling */
#video-controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
box-sizing: border-box;
}
/* Logging display styling */
#logging-display {
position: absolute;
bottom: -150px;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
box-sizing: border-box;
overflow-y: scroll;
height: 140px;
}
</style>
</head>
<body>
<div id="video-container">
<video id="video" width="640" height="480"></video>
<div id="video-controls">
<button id="play-pause-button">Play</button>
<input id="volume-slider" type="range" min="0" max="1" value="1" step="0.01">
<span id="time-display">00:00 / 00:00</span>
</div>
<div id="logging-display"></div>
</div>
<script>
// Get references to HTML elements
const video = document.getElementById('video');
const playPauseButton = document.getElementById('play-pause-button');
const volumeSlider = document.getElementById('volume-slider');
const timeDisplay = document.getElementById('time-display');
const loggingDisplay = document.getElementById('logging-display');
let videoPlaying = false;
let volumeLevel = 1;
let muted = false;
let videoDuration = 0;
let videoCurrentTime = 0;
// Set video source
video.src = 'https://genitive.ai/wp-content/uploads/2025/01/xFactor-Refactoring.mp4';
// When video can play
video.addEventListener('canplay', () => {
videoDuration = video.duration;
// Optionally auto-play:
// video.play();
// videoPlaying = true;
});
// Track video time updates
video.addEventListener('timeupdate', () => {
videoCurrentTime = video.currentTime;
updateTimeDisplay();
});
// When video ends
video.addEventListener('ended', () => {
videoPlaying = false;
playPauseButton.textContent = 'Play';
logMessage('Video ended');
});