2024-06-14 19:40:07 -04:00
/ * B l a c k m a g i c C a m e r a C o n t r o l W e b U I
WebUI Script functions
( c ) Dylan Speiser 2024
github . com / DylanSpeiser
* /
2024-06-11 19:46:25 -04:00
2024-06-13 19:49:36 -04:00
2024-06-14 19:40:07 -04:00
/* Global variables */
var cameras = [ ] ; // Array to store all of the camera objects
var ci = 0 ; // Index into this array for the currently selected camera.
// cameras[ci] is used to reference the currently selected camera object
2024-06-11 19:46:25 -04:00
2024-06-14 19:40:07 -04:00
var WBMode = 0 ; // 0: balance, 1: tint
2024-06-11 19:46:25 -04:00
2024-07-02 17:53:25 -04:00
var defaultControlsHTML ;
2024-06-14 19:40:07 -04:00
// Set everything up
function bodyOnLoad ( ) {
2024-07-02 17:53:25 -04:00
defaultControlsHTML = document . getElementById ( "allCamerasContainer" ) . innerHTML ;
2024-06-11 19:46:25 -04:00
}
2024-07-02 17:53:25 -04:00
// Checks the hostname, if it replies successfully then a new BMCamera object
2024-06-14 19:40:07 -04:00
// is made and gets put in the array at ind
2024-07-02 17:53:25 -04:00
function initCamera ( ) {
// Get hostname from Hostname text field
let hostname = document . getElementById ( "hostnameInput" ) . value ;
try {
// Check if the hostname is valid
let response = sendRequest ( "GET" , "http://" + hostname + "/control/api/v1/system" , "" ) ;
2024-06-13 17:46:46 -04:00
if ( response . status < 300 ) {
2024-06-14 19:40:07 -04:00
// Success, make a new camera, get all relevant info, and populate the UI
2024-07-02 17:53:25 -04:00
cameras [ ci ] = new BMCamera ( hostname ) ;
cameras [ ci ] . updateUI = updateUIAll ;
cameras [ ci ] . active = true ;
2024-06-14 19:40:07 -04:00
document . getElementById ( "connectionErrorSpan" ) . innerHTML = "Connected." ;
document . getElementById ( "connectionErrorSpan" ) . setAttribute ( "style" , "color: #6e6e6e;" ) ;
2024-06-13 17:46:46 -04:00
} else {
2024-06-14 19:40:07 -04:00
// Something has gone wrong, tell the user
2024-06-13 17:46:46 -04:00
document . getElementById ( "connectionErrorSpan" ) . innerHTML = response . statusText ;
}
2024-07-02 17:53:25 -04:00
} catch ( error ) {
2024-06-14 19:40:07 -04:00
// Something has gone wrong, tell the user
2024-06-13 17:46:46 -04:00
document . getElementById ( "connectionErrorSpan" ) . title = error ;
document . getElementById ( "connectionErrorSpan" ) . innerHTML = "Error " + error . code + ": " + error . name + " (Your hostname is probably incorrect, hover for more details)" ;
2024-07-02 17:53:25 -04:00
}
2024-06-13 17:46:46 -04:00
}
2024-07-02 17:53:25 -04:00
// =============================== UI Updater ==================================
// =============================================================================
2024-06-14 19:40:07 -04:00
function updateUIAll ( ) {
2024-07-02 17:53:25 -04:00
// ========== Camera Name ==========
2024-06-14 19:40:07 -04:00
document . getElementById ( "cameraName" ) . innerHTML = cameras [ ci ] . name ;
2024-07-02 17:53:25 -04:00
// ========== Hostname ==========
2024-06-14 19:40:07 -04:00
document . getElementById ( "hostnameInput" ) . value = cameras [ ci ] . hostname ;
2024-07-02 17:53:25 -04:00
// ========== Format ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "formatCodec" ) . innerHTML = cameras [ ci ] . propertyData [ '/system/format' ] ? . codec . toUpperCase ( ) . replace ( ":" , " " ) . replace ( "_" , ":" ) ;
let resObj = cameras [ ci ] . propertyData [ '/system/format' ] ? . recordResolution ;
document . getElementById ( "formatResolution" ) . innerHTML = resObj ? . width + "x" + resObj ? . height ;
document . getElementById ( "formatFPS" ) . innerHTML = cameras [ ci ] . propertyData [ '/system/format' ] ? . frameRate + " fps" ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Recording State ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
if ( cameras [ ci ] . propertyData [ '/transports/0/record' ] ? . recording ) {
2024-06-14 19:40:07 -04:00
document . getElementById ( "cameraControlHeadContainer" ) . classList . add ( "liveCam" ) ;
document . getElementById ( "cameraControlExpandedHeadContainer" ) . classList . add ( "liveCam" ) ;
} else {
document . getElementById ( "cameraControlHeadContainer" ) . classList . remove ( "liveCam" ) ;
document . getElementById ( "cameraControlExpandedHeadContainer" ) . classList . remove ( "liveCam" ) ;
}
2024-07-02 17:53:25 -04:00
// ========== Timecode ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "timecodeLabel" ) . innerHTML = parseTimecode ( cameras [ ci ] . propertyData [ '/transports/0/timecode' ] ? . timecode ) ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Presets Dropdown ==========
2024-06-14 19:40:07 -04:00
var presetsList = document . getElementById ( "presetsDropDown" ) ;
presetsList . innerHTML = "" ;
2024-07-02 17:53:25 -04:00
cameras [ ci ] . propertyData [ '/presets' ] ? . presets . forEach ( ( presetItem ) => {
2024-06-14 19:40:07 -04:00
let presetName = presetItem . split ( '.' , 1 ) ;
let textNode = document . createTextNode ( presetName ) ;
let optionNode = document . createElement ( "option" ) ;
optionNode . setAttribute ( "name" , "presetOption" + presetName ) ;
optionNode . appendChild ( textNode ) ;
document . getElementById ( "presetsDropDown" ) . appendChild ( optionNode ) ;
} ) ;
2024-07-02 17:53:25 -04:00
// ========== Active Preset ==========
2024-06-14 19:40:07 -04:00
var presetsList = document . getElementById ( "presetsDropDown" ) ;
presetsList . childNodes . forEach ( ( child ) => {
2024-07-02 17:53:25 -04:00
if ( child . nodeName == 'OPTION' && child . value == cameras [ ci ] . propertyData [ '/presets/active' ] ? . preset ) {
2024-06-14 19:40:07 -04:00
child . selected = true
} else {
child . selected = false
}
} )
2024-07-02 17:53:25 -04:00
// ========== Iris ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "irisRange" ) . value = cameras [ ci ] . propertyData [ '/lens/iris' ] ? . normalised ;
document . getElementById ( "apertureStopsLabel" ) . innerHTML = cameras [ ci ] . propertyData [ '/lens/iris' ] ? . apertureStop . toFixed ( 1 ) ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Zoom ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "zoomRange" ) . value = cameras [ ci ] . propertyData [ '/lens/zoom' ] ? . normalised ;
document . getElementById ( "zoomMMLabel" ) . innerHTML = cameras [ ci ] . propertyData [ '/lens/zoom' ] ? . focalLength + "mm" ;
// ========== Focus ==========
document . getElementById ( "focusRange" ) . value = cameras [ ci ] . propertyData [ '/lens/focus' ] ? . normalised ;
// ========== ISO ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
if ( cameras [ ci ] . propertyData [ '/video/iso' ] )
document . getElementById ( "ISOInput" ) . value = cameras [ ci ] . propertyData [ '/video/iso' ] ? . iso ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== GAIN ==========
let gainString = "" ;
let gainInt = cameras [ ci ] . propertyData [ '/video/gain' ] ? . gain
if ( gainInt >= 0 ) {
gainString = "+" + gainInt + "db"
2024-06-14 19:40:07 -04:00
} else {
2024-07-02 17:53:25 -04:00
gainString = gainInt + "db"
2024-06-14 19:40:07 -04:00
}
document . getElementById ( "gainSpan" ) . innerHTML = gainString ;
2024-07-02 17:53:25 -04:00
// ========== WHITE BALANCE ===========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "whiteBalanceSpan" ) . innerHTML = cameras [ ci ] . propertyData [ '/video/whiteBalance' ] ? . whiteBalance + "K" ;
document . getElementById ( "whiteBalanceTintSpan" ) . innerHTML = cameras [ ci ] . propertyData [ '/video/whiteBalanceTint' ] ? . whiteBalanceTint ;
// =========== ND =============
if ( cameras [ ci ] . propertyData [ '/video/ndFilter' ] ) {
document . getElementById ( "ndFilterSpan" ) . innerHTML = cameras [ ci ] . propertyData [ '/video/ndFilter' ] ? . stop ;
} else {
2024-06-14 19:40:07 -04:00
document . getElementById ( "ndFilterSpan" ) . innerHTML = 0 ;
document . getElementById ( "ndFilterSpan" ) . disabled = true ;
}
2024-07-02 17:53:25 -04:00
// ============ Shutter =====================
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
let shutterString = "SS"
let shutterObj = cameras [ ci ] . propertyData [ '/video/shutter' ] ;
if ( shutterObj ? . shutterSpeed ) {
shutterString = "1/" + shutterObj . shutterSpeed
} else if ( shutterObj ? . shutterAngle ) {
var shangleString = ( shutterObj . shutterAngle / 100 ) . toFixed ( 1 ) . toString ( )
2024-06-14 19:40:07 -04:00
if ( shangleString . indexOf ( ".0" ) > 0 ) {
shutterString = parseFloat ( shangleString ) . toFixed ( 0 ) + "°" ;
} else {
shutterString = shangleString + "°" ;
}
}
document . getElementById ( "shutterSpan" ) . innerHTML = shutterString ;
2024-07-02 17:53:25 -04:00
// =========== Auto Exposure Mode ===========
2024-06-14 19:40:07 -04:00
let AEmodeSelect = document . getElementById ( "AEmodeDropDown" ) ;
let AEtypeSelect = document . getElementById ( "AEtypeDropDown" ) ;
2024-07-02 17:53:25 -04:00
AEmodeSelect . value = cameras [ ci ] . propertyData [ '/video/autoExposure' ] ? . mode ;
AEtypeSelect . value = cameras [ ci ] . propertyData [ '/video/autoExposure' ] ? . type ;
// =========== COLOR CORRECTION =============
2024-06-14 19:40:07 -04:00
// Lift
2024-07-02 17:53:25 -04:00
let liftProps = cameras [ ci ] . propertyData [ '/colorCorrection/lift' ] ;
document . getElementsByClassName ( "CClumaLabel" ) [ 0 ] . innerHTML = liftProps ? . luma . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCredLabel" ) [ 0 ] . innerHTML = liftProps ? . red . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCgreenLabel" ) [ 0 ] . innerHTML = liftProps ? . green . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCblueLabel" ) [ 0 ] . innerHTML = liftProps ? . blue . toFixed ( 2 ) ;
2024-06-14 19:40:07 -04:00
// Gamma
2024-07-02 17:53:25 -04:00
let gammaProps = cameras [ ci ] . propertyData [ '/colorCorrection/gamma' ] ;
document . getElementsByClassName ( "CClumaLabel" ) [ 1 ] . innerHTML = gammaProps ? . luma . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCredLabel" ) [ 1 ] . innerHTML = gammaProps ? . red . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCgreenLabel" ) [ 1 ] . innerHTML = gammaProps ? . green . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCblueLabel" ) [ 1 ] . innerHTML = gammaProps ? . blue . toFixed ( 2 ) ;
2024-06-14 19:40:07 -04:00
// Gain
2024-07-02 17:53:25 -04:00
let gainProps = cameras [ ci ] . propertyData [ '/colorCorrection/gain' ] ;
document . getElementsByClassName ( "CClumaLabel" ) [ 2 ] . innerHTML = gainProps ? . luma . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCredLabel" ) [ 2 ] . innerHTML = gainProps ? . red . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCgreenLabel" ) [ 2 ] . innerHTML = gainProps ? . green . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCblueLabel" ) [ 2 ] . innerHTML = gainProps ? . blue . toFixed ( 2 ) ;
2024-06-14 19:40:07 -04:00
// Offset
2024-07-02 17:53:25 -04:00
let offsetProps = cameras [ ci ] . propertyData [ '/colorCorrection/offset' ] ;
document . getElementsByClassName ( "CClumaLabel" ) [ 3 ] . innerHTML = offsetProps ? . luma . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCredLabel" ) [ 3 ] . innerHTML = offsetProps ? . red . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCgreenLabel" ) [ 3 ] . innerHTML = offsetProps ? . green . toFixed ( 2 ) ;
document . getElementsByClassName ( "CCblueLabel" ) [ 3 ] . innerHTML = offsetProps ? . blue . toFixed ( 2 ) ;
2024-06-14 19:40:07 -04:00
// Contrast
2024-07-02 17:53:25 -04:00
let constrastProps = cameras [ ci ] . propertyData [ '/colorCorrection/contrast' ] ;
document . getElementById ( "CCcontrastPivotRange" ) . value = constrastProps ? . pivot ;
document . getElementById ( "CCcontrastPivotLabel" ) . innerHTML = constrastProps ? . pivot . toFixed ( 2 ) ;
document . getElementById ( "CCcontrastAdjustRange" ) . value = constrastProps ? . adjust ;
document . getElementById ( "CCcontrastAdjustLabel" ) . innerHTML = parseInt ( constrastProps ? . adjust * 50 ) + "%" ;
2024-06-14 19:40:07 -04:00
// Color
2024-07-02 17:53:25 -04:00
let colorProps = cameras [ ci ] . propertyData [ '/colorCorrection/color' ] ;
document . getElementById ( "CChueRange" ) . value = colorProps ? . hue ;
document . getElementById ( "CCcolorHueLabel" ) . innerHTML = parseInt ( ( colorProps ? . hue + 1 ) * 180 ) + "°" ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "CCsaturationRange" ) . value = colorProps ? . saturation ;
document . getElementById ( "CCcolorSatLabel" ) . innerHTML = parseInt ( colorProps ? . saturation * 50 ) + "%" ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
let lumaContributionProps = cameras [ ci ] . propertyData [ '/colorCorrection/lumaContribution' ] ;
document . getElementById ( "CClumaContributionRange" ) . value = lumaContributionProps ? . lumaContribution ;
document . getElementById ( "CCcolorLCLabel" ) . innerHTML = parseInt ( lumaContributionProps ? . lumaContribution * 100 ) + "%" ;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ============ Footer Links ===============
2024-06-14 19:40:07 -04:00
document . getElementById ( "documentationLink" ) . href = "http://" + cameras [ ci ] . hostname + "/control/documentation.html" ;
document . getElementById ( "mediaManagerLink" ) . href = "http://" + cameras [ ci ] . hostname ;
}
// ==============================================================================
2024-07-02 17:53:25 -04:00
// Called when the user changes tabs to a different camera
function switchCamera ( index ) {
2024-06-12 20:18:19 -04:00
if ( cameras [ ci ] ) {
2024-07-02 17:53:25 -04:00
cameras [ ci ] . active = false ;
2024-06-12 20:18:19 -04:00
}
2024-06-11 19:46:25 -04:00
2024-06-12 20:18:19 -04:00
ci = index ;
2024-07-02 17:53:25 -04:00
// Reset the Controls
document . getElementById ( "allCamerasContainer" ) . innerHTML = defaultControlsHTML ;
// Update the UI
2024-06-12 20:18:19 -04:00
for ( var i = 0 ; i < 8 ; i ++ ) {
if ( i == ci ) {
document . getElementsByClassName ( "cameraSwitchLabel" ) [ i ] . classList . add ( "selectedCam" ) ;
} else {
document . getElementsByClassName ( "cameraSwitchLabel" ) [ i ] . classList . remove ( "selectedCam" ) ;
}
}
document . getElementById ( "cameraNumberLabel" ) . innerHTML = "CAM" + ( ci + 1 ) ;
2024-06-13 19:49:36 -04:00
document . getElementById ( "cameraName" ) . innerHTML = "CAMERA NAME" ;
2024-07-02 17:53:25 -04:00
if ( cameras [ ci ] ) {
cameras [ ci ] . active = true ;
}
2024-06-12 20:18:19 -04:00
}
2024-06-14 19:40:07 -04:00
// For not-yet-implemented Color Correction UI
2024-06-12 20:18:19 -04:00
function setCCMode ( mode ) {
if ( mode == 0 ) {
// Lift
} else if ( mode == 1 ) {
// Gamma
} else {
// Gain
}
for ( var i = 0 ; i < 3 ; i ++ ) {
if ( i == mode ) {
document . getElementsByClassName ( "ccTabLabel" ) [ i ] . classList . add ( "selectedTab" ) ;
} else {
document . getElementsByClassName ( "ccTabLabel" ) [ i ] . classList . remove ( "selectedTab" ) ;
}
}
}
2024-06-14 19:40:07 -04:00
// Allows for changing WB/Tint displayed in the UI
2024-06-13 17:46:46 -04:00
function swapWBMode ( ) {
if ( WBMode == 0 ) {
// Balance
document . getElementById ( "WBLabel" ) . innerHTML = "TINT" ;
document . getElementById ( "WBValueContainer" ) . classList . add ( "dNone" ) ;
document . getElementById ( "WBTintValueContainer" ) . classList . remove ( "dNone" ) ;
WBMode = 1 ;
} else {
//Tint
document . getElementById ( "WBLabel" ) . innerHTML = "BALANCE" ;
document . getElementById ( "WBValueContainer" ) . classList . remove ( "dNone" ) ;
document . getElementById ( "WBTintValueContainer" ) . classList . add ( "dNone" ) ;
WBMode = 0 ;
}
}
2024-06-14 19:40:07 -04:00
// Triggered by the button by those text boxes. Reads the info from the inputs and sends it to the camera.
2024-06-13 17:46:46 -04:00
function manualAPICall ( ) {
const requestRadioGET = document . getElementById ( "requestTypeGET" ) ;
const requestEndpointText = document . getElementById ( "manualRequestEndpointLabel" ) . value ;
let requestData = "" ;
try {
requestData = JSON . parse ( document . getElementById ( "manualRequestBodyLabel" ) . value ) ;
} catch ( err ) {
document . getElementById ( "manualRequestResponseP" ) . innerHTML = err ;
}
const requestMethod = ( requestRadioGET . checked ? "GET" : "PUT" ) ;
const requestURL = cameras [ ci ] . APIAddress + requestEndpointText ;
2024-07-02 17:53:25 -04:00
let response = sendRequest ( requestMethod , requestURL , requestData ) ;
2024-06-12 20:18:19 -04:00
2024-07-02 17:53:25 -04:00
document . getElementById ( "manualRequestResponseP" ) . innerHTML = JSON . stringify ( response ) ;
2024-06-12 20:18:19 -04:00
}
2024-07-02 17:53:25 -04:00
/* Helper Functions */
function parseTimecode ( timecodeBCD ) {
let noDropFrame = timecodeBCD & 0b01111111111111111111111111111111 ; // The first bit of the timecode is 1 if "Drop Frame Timecode" is on. We don't want to include that in the display.
let decimalTCInt = parseInt ( noDropFrame . toString ( 16 ) , 10 ) ; // Convert the BCD number into base ten
let decimalTCString = decimalTCInt . toString ( ) . padStart ( 8 , '0' ) ; // Convert the base ten number to a string eight characters long
let finalTCString = decimalTCString . match ( /.{1,2}/g ) . join ( ':' ) ; // Put colons between every two characters
return finalTCString ;
2024-06-11 19:46:25 -04:00
}