2020-09-15 06:04:45 +08:00
/*
2020-10-07 08:08:18 +08:00
* simpleviewer and streamviewer
2020-09-15 06:04:45 +08:00
*/
2020-10-07 18:19:19 +08:00
const uint8_t index_simple_html [ ] = R " =====(<!doctype html>
2020-09-11 01:54:00 +08:00
< html >
< head >
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width,initial-scale=1 " >
2020-10-07 08:08:18 +08:00
< title id = " title " > ESP32 - CAM Simplified View < / title >
2020-09-11 01:54:00 +08:00
< link rel = " icon " type = " image/png " sizes = " 32x32 " href = " /favicon-32x32.png " >
< link rel = " icon " type = " image/png " sizes = " 16x16 " href = " /favicon-16x16.png " >
2020-09-13 21:28:38 +08:00
< link rel = " stylesheet " type = " text/css " href = " /style.css " >
2020-09-11 01:54:00 +08:00
< style >
2021-10-11 18:44:58 +08:00
@ media ( min - width : 800 px ) and ( orientation : landscape ) {
# content {
display : flex ;
flex - wrap : nowrap ;
flex - direction : column ;
align - items : flex - start ;
}
}
2020-09-11 01:54:00 +08:00
< / style >
< / head >
< body >
< section class = " main " >
< div id = " logo " >
< label for = " nav-toggle-cb " id = " nav-toggle " style = " float:left; " title = " Settings " > & # 9776 ; & nbsp ; < / label >
2020-09-13 21:28:38 +08:00
< button id = " swap-viewer " style = " float:left; " title = " Swap to full feature viewer " > Full < / button >
2020-09-11 01:54:00 +08:00
< button id = " get-still " style = " float:left; " > Get Still < / button >
< button id = " toggle-stream " style = " float:left; " class = " hidden " > Start Stream < / button >
< div id = " wait-settings " style = " float:left; " class = " loader " title = " Waiting for camera settings to load " > < / div >
< / div >
< div id = " content " >
< div class = " hidden " id = " sidebar " >
< input type = " checkbox " id = " nav-toggle-cb " >
2020-09-13 21:28:38 +08:00
< nav id = " menu " style = " width:24em; " >
2022-03-10 21:44:26 +08:00
< div class = " input-group hidden " id = " lamp-group " title = " Flashlight LED.

Warning:
Built-In lamps can be Very Bright! Avoid looking directly at LED
Can draw a lot of power and may cause visual artifacts, affect WiFi or even brownout the camera on high settings " >
2020-09-11 01:54:00 +08:00
< label for = " lamp " > Light < / label >
< div class = " range-min " > Off < / div >
< input type = " range " id = " lamp " min = " 0 " max = " 100 " value = " 0 " class = " action-setting " >
2022-03-09 18:58:54 +08:00
< div class = " range-max " > Full & # 9888 ; < / div >
2020-09-13 21:28:38 +08:00
< / div >
< div class = " input-group " id = " framesize-group " >
< label for = " framesize " > Resolution < / label >
< select id = " framesize " class = " action-setting " >
2021-05-03 16:35:12 +08:00
< option value = " 13 " > UXGA ( 1600 x1200 ) < / option >
< option value = " 12 " > SXGA ( 1280 x1024 ) < / option >
< option value = " 11 " > HD ( 1280 x720 ) < / option >
< option value = " 10 " > XGA ( 1024 x768 ) < / option >
< option value = " 9 " > SVGA ( 800 x600 ) < / option >
< option value = " 8 " > VGA ( 640 x480 ) < / option >
< option value = " 7 " > HVGA ( 480 x320 ) < / option >
< option value = " 6 " > CIF ( 400 x296 ) < / option >
< option value = " 5 " > QVGA ( 320 x240 ) < / option >
< option value = " 3 " > HQVGA ( 240 x176 ) < / option >
< option value = " 1 " > QQVGA ( 160 x120 ) < / option >
< option value = " 0 " > THUMB ( 96 x96 ) < / option >
2020-09-13 21:28:38 +08:00
< / select >
< / div >
< ! - - Hide the next entries , they are present in the body so that we
can pass settings to / from them for use in the scripting , not for user setting - - >
< div id = " rotate " class = " action-setting hidden " > < / div >
< div id = " cam_name " class = " action-setting hidden " > < / div >
< div id = " stream_url " class = " action-setting hidden " > < / div >
< / nav >
2020-09-11 01:54:00 +08:00
< / div >
< figure >
< div id = " stream-container " class = " image-container hidden " >
2020-09-13 21:28:38 +08:00
< div class = " close close-rot-none " id = " close-stream " > × < / div >
2020-09-11 01:54:00 +08:00
< img id = " stream " src = " " >
< / div >
< / figure >
< / div >
< / section >
< / body >
< script >
document . addEventListener ( ' DOMContentLoaded ' , function ( event ) {
var baseHost = document . location . origin ;
var streamURL = ' Undefined ' ;
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const settings = document . getElementById ( ' sidebar ' )
const waitSettings = document . getElementById ( ' wait - settings ' )
const lampGroup = document . getElementById ( ' lamp - group ' )
const rotate = document . getElementById ( ' rotate ' )
const view = document . getElementById ( ' stream ' )
const viewContainer = document . getElementById ( ' stream - container ' )
const stillButton = document . getElementById ( ' get - still ' )
const streamButton = document . getElementById ( ' toggle - stream ' )
const closeButton = document . getElementById ( ' close - stream ' )
2020-09-13 21:28:38 +08:00
const swapButton = document . getElementById ( ' swap - viewer ' )
2020-09-11 01:54:00 +08:00
const hide = el = > {
el . classList . add ( ' hidden ' )
}
const show = el = > {
el . classList . remove ( ' hidden ' )
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const disable = el = > {
el . classList . add ( ' disabled ' )
el . disabled = true
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const enable = el = > {
el . classList . remove ( ' disabled ' )
el . disabled = false
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const updateValue = ( el , value , updateRemote ) = > {
updateRemote = updateRemote = = null ? true : updateRemote
let initialValue
if ( el . type = = = ' checkbox ' ) {
initialValue = el . checked
value = ! ! value
el . checked = value
} else {
initialValue = el . value
el . value = value
}
2020-09-15 06:04:45 +08:00
2020-09-11 01:54:00 +08:00
if ( updateRemote & & initialValue ! = = value ) {
updateConfig ( el ) ;
} else if ( ! updateRemote ) {
if ( el . id = = = " lamp " ) {
2022-03-09 15:40:00 +08:00
if ( value = = - 1 ) {
2020-09-11 01:54:00 +08:00
hide ( lampGroup )
} else {
show ( lampGroup )
}
} else if ( el . id = = = " cam_name " ) {
window . document . title = value ;
console . log ( ' Name set to : ' + value ) ;
2020-09-13 21:28:38 +08:00
} else if ( el . id = = = " code_ver " ) {
console . log ( ' Firmware Build : ' + value ) ;
2020-09-11 01:54:00 +08:00
} else if ( el . id = = = " rotate " ) {
rotate . value = value ;
applyRotation ( ) ;
} else if ( el . id = = = " stream_url " ) {
streamURL = value ;
2020-10-26 07:04:21 +08:00
streamButton . setAttribute ( " title " , ` Start the stream : : { streamURL } ` ) ;
2020-09-11 01:54:00 +08:00
console . log ( ' Stream URL set to : ' + value ) ;
2022-03-09 15:40:00 +08:00
}
2020-09-11 01:54:00 +08:00
}
}
2020-09-13 21:28:38 +08:00
2021-09-26 09:00:15 +08:00
var rangeUpdateScheduled = false
var latestRangeConfig
function updateRangeConfig ( el ) {
latestRangeConfig = el
if ( ! rangeUpdateScheduled ) {
rangeUpdateScheduled = true ;
setTimeout ( function ( ) {
rangeUpdateScheduled = false
updateConfig ( latestRangeConfig )
} , 150 ) ;
}
}
2020-09-11 01:54:00 +08:00
function updateConfig ( el ) {
let value
switch ( el . type ) {
case ' checkbox ' :
value = el . checked ? 1 : 0
break
case ' range ' :
case ' select - one ' :
value = el . value
break
case ' button ' :
case ' submit ' :
value = ' 1 '
break
default :
return
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const query = ` $ { baseHost } / control ? var = $ { el . id } & val = $ { value } `
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
fetch ( query )
. then ( response = > {
console . log ( ` request to $ { query } finished , status : $ { response . status } ` )
} )
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
document
. querySelectorAll ( ' . close ' )
. forEach ( el = > {
el . onclick = ( ) = > {
hide ( el . parentNode )
}
} )
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
// read initial values
fetch ( ` $ { baseHost } / status ` )
. then ( function ( response ) {
return response . json ( )
} )
. then ( function ( state ) {
document
. querySelectorAll ( ' . action - setting ' )
. forEach ( el = > {
updateValue ( el , state [ el . id ] , false )
} )
hide ( waitSettings ) ;
show ( settings ) ;
show ( streamButton ) ;
startStream ( ) ;
} )
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
// Put some helpful text on the 'Still' button
2020-10-26 07:04:21 +08:00
stillButton . setAttribute ( " title " , ` Capture a still image : : $ { baseHost } / capture ` ) ;
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const stopStream = ( ) = > {
window . stop ( ) ;
streamButton . innerHTML = ' Start Stream ' ;
2020-10-26 07:04:21 +08:00
streamButton . setAttribute ( " title " , ` Start the stream : : $ { streamURL } ` ) ;
2020-09-11 01:54:00 +08:00
hide ( viewContainer ) ;
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
const startStream = ( ) = > {
view . src = streamURL ;
view . scrollIntoView ( false ) ;
streamButton . innerHTML = ' Stop Stream ' ;
2020-10-26 07:04:21 +08:00
streamButton . setAttribute ( " title " , ` Stop the stream ` ) ;
2020-09-11 01:54:00 +08:00
show ( viewContainer ) ;
}
const applyRotation = ( ) = > {
rot = rotate . value ;
if ( rot = = - 90 ) {
viewContainer . style . transform = ` rotate ( - 90 deg ) translate ( - 100 % ) ` ;
2020-09-13 21:28:38 +08:00
closeButton . classList . remove ( ' close - rot - none ' ) ;
closeButton . classList . remove ( ' close - rot - right ' ) ;
closeButton . classList . add ( ' close - rot - left ' ) ;
2020-09-11 01:54:00 +08:00
} else if ( rot = = 90 ) {
2020-09-13 21:28:38 +08:00
viewContainer . style . transform = ` rotate ( 90 deg ) translate ( 0 , - 100 % ) ` ;
closeButton . classList . remove ( ' close - rot - left ' ) ;
closeButton . classList . remove ( ' close - rot - none ' ) ;
closeButton . classList . add ( ' close - rot - right ' ) ;
2020-09-11 01:54:00 +08:00
} else {
2020-09-13 21:28:38 +08:00
viewContainer . style . transform = ` rotate ( 0 deg ) ` ;
closeButton . classList . remove ( ' close - rot - left ' ) ;
closeButton . classList . remove ( ' close - rot - right ' ) ;
closeButton . classList . add ( ' close - rot - none ' ) ;
2020-09-11 01:54:00 +08:00
}
console . log ( ' Rotation ' + rot + ' applied ' ) ;
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
// Attach actions to controls
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
stillButton . onclick = ( ) = > {
stopStream ( ) ;
view . src = ` $ { baseHost } / capture ? _cb = $ { Date . now ( ) } ` ;
view . scrollIntoView ( false ) ;
show ( viewContainer ) ;
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
closeButton . onclick = ( ) = > {
stopStream ( ) ;
hide ( viewContainer ) ;
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
streamButton . onclick = ( ) = > {
const streamEnabled = streamButton . innerHTML = = = ' Stop Stream '
if ( streamEnabled ) {
stopStream ( ) ;
} else {
startStream ( ) ;
}
}
2020-09-13 21:28:38 +08:00
2020-09-11 01:54:00 +08:00
// Attach default on change action
document
. querySelectorAll ( ' . action - setting ' )
. forEach ( el = > {
el . onchange = ( ) = > updateConfig ( el )
} )
2020-09-13 21:28:38 +08:00
2021-09-26 09:00:15 +08:00
// Update range sliders as they are being moved
document
. querySelectorAll ( ' input [ type = " range " ] ' )
. forEach ( el = > {
el . oninput = ( ) = > updateRangeConfig ( el )
} )
2020-09-11 01:54:00 +08:00
// Custom actions
// Detection and framesize
rotate . onchange = ( ) = > {
applyRotation ( ) ;
updateConfig ( rotate ) ;
}
2020-09-13 21:28:38 +08:00
framesize . onchange = ( ) = > {
updateConfig ( framesize )
}
swapButton . onclick = ( ) = > {
2020-10-07 08:08:18 +08:00
window . open ( ' / ? view = full ' , ' _self ' ) ;
2020-09-13 21:28:38 +08:00
}
} )
2020-09-11 01:54:00 +08:00
< / script >
2020-10-07 08:08:18 +08:00
< / html > ) = = = = = " ;
2020-09-13 21:28:38 +08:00
2020-10-07 08:08:18 +08:00
size_t index_simple_html_len = sizeof ( index_simple_html ) - 1 ;
2020-09-15 06:04:45 +08:00
/* Stream Viewer */
2020-10-07 18:19:19 +08:00
const uint8_t streamviewer_html [ ] = R " =====(<!doctype html>
2020-09-15 06:04:45 +08:00
< html >
< head >
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width,initial-scale=1 " >
< title id = " title " > ESP32 - CAM StreamViewer < / title >
< link rel = " icon " type = " image/png " sizes = " 32x32 " href = " /favicon-32x32.png " >
< link rel = " icon " type = " image/png " sizes = " 16x16 " href = " /favicon-16x16.png " >
< style >
/* No stylesheet, define all style elements here */
body {
font - family : Arial , Helvetica , sans - serif ;
background : # 181818 ;
color : # EFEFEF ;
font - size : 16 px ;
margin : 0 px ;
2020-10-04 01:49:08 +08:00
overflow : hidden ;
2020-09-15 06:04:45 +08:00
}
2020-10-04 01:49:08 +08:00
img {
2020-09-15 06:04:45 +08:00
object - fit : contain ;
display : block ;
2020-10-04 01:49:08 +08:00
margin : 0 px ;
padding : 0 px ;
2020-09-15 06:04:45 +08:00
width : 100 vw ;
height : 100 vh ;
}
. loader {
border : 0.5 em solid # f3f3f3 ;
border - top : 0.5 em solid # 000000 ;
border - radius : 50 % ;
width : 1 em ;
height : 1 em ;
- webkit - animation : spin 2 s linear infinite ; /* Safari */
animation : spin 2 s linear infinite ;
}
@ - webkit - keyframes spin { /* Safari */
0 % { - webkit - transform : rotate ( 0 deg ) ; }
100 % { - webkit - transform : rotate ( 360 deg ) ; }
}
@ keyframes spin {
0 % { transform : rotate ( 0 deg ) ; }
100 % { transform : rotate ( 360 deg ) ; }
}
< / style >
< / head >
< body >
< section class = " main " >
2020-10-04 01:49:08 +08:00
< div id = " wait-settings " style = " float:left; " class = " loader " title = " Waiting for stream settings to load " > < / div >
< div style = " display: none; " >
< ! - - Hide the next entries , they are present in the body so that we
can pass settings to / from them for use in the scripting - - >
< div id = " rotate " class = " action-setting hidden " > 0 < / div >
< div id = " cam_name " class = " action-setting hidden " > < / div >
< div id = " stream_url " class = " action-setting hidden " > < / div >
2020-09-15 06:04:45 +08:00
< / div >
2020-10-04 01:49:08 +08:00
< img id = " stream " src = " " >
2020-09-15 06:04:45 +08:00
< / section >
< / body >
< script >
document . addEventListener ( ' DOMContentLoaded ' , function ( event ) {
var baseHost = document . location . origin ;
2020-10-04 01:49:08 +08:00
var streamURL = ' Undefined ' ;
2020-09-15 06:04:45 +08:00
const rotate = document . getElementById ( ' rotate ' )
2020-10-04 01:49:08 +08:00
const stream = document . getElementById ( ' stream ' )
2020-09-15 06:04:45 +08:00
const spinner = document . getElementById ( ' wait - settings ' )
const updateValue = ( el , value , updateRemote ) = > {
updateRemote = updateRemote = = null ? true : updateRemote
let initialValue
if ( el . type = = = ' checkbox ' ) {
initialValue = el . checked
value = ! ! value
el . checked = value
} else {
initialValue = el . value
el . value = value
}
if ( updateRemote & & initialValue ! = = value ) {
updateConfig ( el ) ;
} else if ( ! updateRemote ) {
if ( el . id = = = " cam_name " ) {
window . document . title = value ;
2020-10-04 01:49:08 +08:00
stream . setAttribute ( " title " , value + " \n (doubleclick for fullscreen) " ) ;
2020-09-15 06:04:45 +08:00
console . log ( ' Name set to : ' + value ) ;
} else if ( el . id = = = " rotate " ) {
rotate . value = value ;
2020-10-04 01:49:08 +08:00
console . log ( ' Rotate recieved : ' + rotate . value ) ;
2020-09-15 06:04:45 +08:00
} else if ( el . id = = = " stream_url " ) {
streamURL = value ;
console . log ( ' Stream URL set to : ' + value ) ;
2022-03-09 15:40:00 +08:00
}
2020-09-15 06:04:45 +08:00
}
}
// read initial values
fetch ( ` $ { baseHost } / info ` )
. then ( function ( response ) {
return response . json ( )
} )
. then ( function ( state ) {
document
. querySelectorAll ( ' . action - setting ' )
. forEach ( el = > {
updateValue ( el , state [ el . id ] , false )
} )
spinner . style . display = ` none ` ;
2020-10-04 01:49:08 +08:00
applyRotation ( ) ;
2020-09-15 06:04:45 +08:00
startStream ( ) ;
} )
const startStream = ( ) = > {
2020-10-04 01:49:08 +08:00
stream . src = streamURL ;
stream . style . display = ` block ` ;
2020-09-15 06:04:45 +08:00
}
const applyRotation = ( ) = > {
rot = rotate . value ;
if ( rot = = - 90 ) {
2020-10-04 01:49:08 +08:00
stream . style . transform = ` rotate ( - 90 deg ) ` ;
2020-09-15 06:04:45 +08:00
} else if ( rot = = 90 ) {
2020-10-04 01:49:08 +08:00
stream . style . transform = ` rotate ( 90 deg ) ` ;
2020-09-15 06:04:45 +08:00
}
2020-10-04 01:49:08 +08:00
console . log ( ' Rotation ' + rot + ' applied ' ) ;
}
2020-09-15 06:04:45 +08:00
2020-10-04 01:49:08 +08:00
stream . ondblclick = ( ) = > {
if ( stream . requestFullscreen ) {
stream . requestFullscreen ( ) ;
} else if ( stream . mozRequestFullScreen ) { /* Firefox */
stream . mozRequestFullScreen ( ) ;
} else if ( stream . webkitRequestFullscreen ) { /* Chrome, Safari and Opera */
stream . webkitRequestFullscreen ( ) ;
} else if ( stream . msRequestFullscreen ) { /* IE/Edge */
stream . msRequestFullscreen ( ) ;
2020-09-15 06:04:45 +08:00
}
}
} )
< / script >
2020-10-07 08:08:18 +08:00
< / html > ) = = = = = " ;
2020-09-15 06:04:45 +08:00
2020-10-07 08:08:18 +08:00
size_t streamviewer_html_len = sizeof ( streamviewer_html ) - 1 ;
2022-03-09 15:40:00 +08:00
/* Captive Portal page
2020-10-07 18:19:19 +08:00
we replace the < > delimited strings with correct values as it is served */
2020-10-07 08:08:18 +08:00
2020-10-07 18:19:19 +08:00
const std : : string portal_html = R " =====(<!doctype html>
< html >
< head >
2020-10-07 08:08:18 +08:00
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width,initial-scale=1 " >
2020-10-07 18:19:19 +08:00
< title id = " title " > < CAMNAME > - portal < / title >
< link rel = " icon " type = " image/png " sizes = " 32x32 " href = " <APPURL>favicon-32x32.png " >
< link rel = " icon " type = " image/png " sizes = " 16x16 " href = " <APPURL>favicon-16x16.png " >
< link rel = " stylesheet " type = " text/css " href = " <APPURL>style.css " >
< / head >
< body style = " text-align: center; " >
< img src = " <APPURL>logo.svg " style = " position: relative; float: right; " >
< h1 > < CAMNAME > - access portal < / h1 >
< div class = " input-group " style = " margin: auto; width: max-content; " >
< a href = " <APPURL>?view=simple " title = " Click here for a simple view with minimum control " style = " text-decoration: none; " target = " _blank " >
< button > Simple Viewer < / button > < / a >
< a href = " <APPURL>?view=full " title = " Click here for the main camera page with full controls " style = " text-decoration: none; " target = " _blank " >
< button > Full Viewer < / button > < / a >
< a href = " <STREAMURL>view " title = " Click here for the dedicated stream viewer " style = " text-decoration: none; " target = " _blank " >
< button > Stream Viewer < / button > < / a >
2020-10-07 08:08:18 +08:00
< / div >
< hr >
2020-10-07 18:19:19 +08:00
< a href = " <APPURL>dump " title = " Information dump page " target = " _blank " > Camera Details < / a > < br >
< / body >
< / html > ) = = = = = " ;
2020-11-21 00:05:00 +08:00
2022-03-09 15:40:00 +08:00
/* Error page
2020-11-21 00:05:00 +08:00
we replace the < > delimited strings with correct values as it is served */
const std : : string error_html = R " =====(<!doctype html>
< html >
< head >
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width,initial-scale=1 " >
< title id = " title " > < CAMNAME > - Error < / title >
< link rel = " icon " type = " image/png " sizes = " 32x32 " href = " /favicon-32x32.png " >
< link rel = " ico \" type= " image / png " sizes= " 16 x16 " href= " / favicon - 16 x16 . png " >
< link rel = " stylesheet " type = " text/css " href = " <APPURL>style.css " >
< / head >
< body style = " text-align: center; " >
< img src = " <APPURL>logo.svg " style = " position: relative; float: right; " >
< h1 > < CAMNAME > < / h1 >
< ERRORTEXT >
< / body >
< script >
setTimeout ( function ( ) {
location . replace ( document . URL ) ;
} , 60000 ) ;
< / script >
< / html > ) = = = = = " ;