···
2
2
3
3
import androidx.compose.animation.AnimatedContent
4
4
import androidx.compose.foundation.Image
5
5
+
import androidx.compose.foundation.layout.Arrangement
5
6
import androidx.compose.foundation.layout.Box
6
7
import androidx.compose.foundation.layout.Column
8
8
+
import androidx.compose.foundation.layout.Row
9
9
+
import androidx.compose.foundation.layout.Spacer
7
10
import androidx.compose.foundation.layout.WindowInsets
8
11
import androidx.compose.foundation.layout.fillMaxSize
9
12
import androidx.compose.foundation.layout.fillMaxWidth
13
13
+
import androidx.compose.foundation.layout.height
10
14
import androidx.compose.foundation.layout.imePadding
11
15
import androidx.compose.foundation.layout.padding
12
16
import androidx.compose.foundation.layout.safeDrawing
17
17
+
import androidx.compose.foundation.layout.width
13
18
import androidx.compose.foundation.layout.windowInsetsPadding
14
19
import androidx.compose.material3.Button
15
20
import androidx.compose.material3.CircularProgressIndicator
21
21
+
import androidx.compose.material3.DropdownMenu
22
22
+
import androidx.compose.material3.DropdownMenuItem
23
23
+
import androidx.compose.material3.OutlinedButton
16
24
import androidx.compose.material3.Tab
17
25
import androidx.compose.material3.TabRow
18
26
import androidx.compose.material3.Text
27
27
+
import androidx.compose.material3.TextButton
28
28
+
import androidx.compose.material3.Icon
29
29
+
import androidx.compose.material3.IconButton
30
30
+
import androidx.compose.material3.Switch
31
31
+
import androidx.compose.material3.HorizontalDivider
19
32
import androidx.compose.runtime.Composable
20
33
import androidx.compose.runtime.DisposableEffect
21
34
import androidx.compose.runtime.LaunchedEffect
22
35
import androidx.compose.runtime.collectAsState
23
36
import androidx.compose.runtime.getValue
37
37
+
import androidx.compose.runtime.key
24
38
import androidx.compose.runtime.mutableStateOf
25
39
import androidx.compose.runtime.remember
26
40
import androidx.compose.runtime.rememberCoroutineScope
···
32
46
import androidx.compose.ui.graphics.ImageBitmap
33
47
import androidx.compose.ui.graphics.drawscope.Stroke
34
48
import androidx.compose.ui.layout.ContentScale
49
49
+
import androidx.compose.ui.text.font.FontWeight
35
50
import androidx.compose.ui.unit.dp
36
51
import androidx.compose.ui.unit.sp
37
52
import chaintech.videoplayer.host.MediaPlayerHost
···
304
319
}
305
320
}
306
321
322
322
+
private enum class ZoomChoice(val label: String) {
323
323
+
ZOOM_1X("1.0x"),
324
324
+
ZOOM_0_5X("0.5x"),
325
325
+
}
326
326
+
307
327
@OptIn(ExperimentalTime::class)
308
328
@Composable
309
329
fun CameraSample() {
···
320
340
var frontCamera by remember { mutableStateOf(false) }
321
341
var ultrawide by remember { mutableStateOf(false) }
322
342
var previewFillMode by remember { mutableStateOf(PreviewFillMode.FIT) }
343
343
+
var menuExpanded by remember { mutableStateOf(false) }
344
344
+
323
345
val controller = remember { CameraViewControllerImpl() }
324
346
PermissionProvider().apply {
325
347
if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
326
348
permissionGranted = true
327
349
}, onDenied = { permissionGranted = false }) else permissionGranted = true
328
350
}
351
351
+
329
352
if (permissionGranted) {
330
330
-
Box(modifier = Modifier.fillMaxSize()) {
331
331
-
Column(modifier = Modifier.fillMaxSize()) {
332
332
-
androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) {
333
333
-
val playerHost = remember(path) {
334
334
-
MediaPlayerHost(
335
335
-
mediaUrl = path,
336
336
-
isLooping = true,
337
337
-
isPaused = false,
338
338
-
isMuted = false,
339
339
-
initialVideoFitMode = ScreenResize.FIT,
353
353
+
DetectOrientation{ orientation ->
354
354
+
Box(modifier = Modifier.fillMaxSize()) {
355
355
+
Column(modifier = Modifier.fillMaxSize()) {
356
356
+
androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) {
357
357
+
val playerHost = remember(path) {
358
358
+
MediaPlayerHost(
359
359
+
mediaUrl = path,
360
360
+
isLooping = true,
361
361
+
isPaused = false,
362
362
+
isMuted = false,
363
363
+
initialVideoFitMode = ScreenResize.FIT,
364
364
+
)
365
365
+
}
366
366
+
VideoPlayerComposable(
367
367
+
modifier = Modifier.weight(1f),
368
368
+
playerHost = playerHost,
369
369
+
playerConfig = VideoPlayerConfig(
370
370
+
isSeekBarVisible = true,
371
371
+
isDurationVisible = true,
372
372
+
isFastForwardBackwardEnabled = true,
373
373
+
isMuteControlEnabled = true,
374
374
+
isSpeedControlEnabled = true,
375
375
+
isScreenLockEnabled = false,
376
376
+
isScreenResizeEnabled = true,
377
377
+
isFullScreenEnabled = true,
378
378
+
isPauseResumeEnabled = true,
379
379
+
)
340
380
)
341
381
}
342
342
-
VideoPlayerComposable(
343
343
-
modifier = Modifier.weight(1f),
344
344
-
playerHost = playerHost,
345
345
-
playerConfig = VideoPlayerConfig(
346
346
-
isSeekBarVisible = true,
347
347
-
isDurationVisible = true,
348
348
-
isFastForwardBackwardEnabled = true,
349
349
-
isMuteControlEnabled = true,
350
350
-
isSpeedControlEnabled = true,
351
351
-
isScreenLockEnabled = false,
352
352
-
isScreenResizeEnabled = true,
353
353
-
isFullScreenEnabled = true,
354
354
-
isPauseResumeEnabled = true,
382
382
+
key(orientation){
383
383
+
CameraView(
384
384
+
skeletonRepository = skeletonRepository,
385
385
+
customObjectRepository = customObjectRespository,
386
386
+
detectMode = DetectMode.BOTH,
387
387
+
drawSkeleton = true,
388
388
+
drawObjects = { obj ->
389
389
+
obj.flatMap {
390
390
+
listOf(
391
391
+
DrawableObject(
392
392
+
obj = it,
393
393
+
shape = DrawableShape.LABEL,
394
394
+
colour = Color.Yellow,
395
395
+
style = Stroke(it.boundingBox.width * 0.1f)
396
396
+
), DrawableObject(
397
397
+
obj = it,
398
398
+
shape = DrawableShape.RECTANGLE,
399
399
+
colour = Color.Green,
400
400
+
style = Stroke(it.boundingBox.width * 0.1f)
401
401
+
)
402
402
+
)
403
403
+
}
404
404
+
},
405
405
+
objectModel = generalModel,
406
406
+
modifier = Modifier.weight(1f),
407
407
+
focusArea = Rect(0f,0f,1f,1f),
408
408
+
frontCamera = frontCamera,
409
409
+
useUltraWide = ultrawide,
410
410
+
previewFillMode = previewFillMode,
411
411
+
recordingId = recordingId,
412
412
+
controller = controller,
413
413
+
onVideoSaved = { id, url -> path = url },
355
414
)
356
356
-
)
415
415
+
}
357
416
}
358
358
-
CameraView(
359
359
-
skeletonRepository = skeletonRepository,
360
360
-
customObjectRepository = customObjectRespository,
361
361
-
detectMode = DetectMode.BOTH,
362
362
-
drawSkeleton = true,
363
363
-
drawObjects = { obj ->
364
364
-
obj.flatMap {
365
365
-
listOf(
366
366
-
DrawableObject(
367
367
-
obj = it,
368
368
-
shape = DrawableShape.LABEL,
369
369
-
colour = Color.Yellow,
370
370
-
style = Stroke(it.boundingBox.width * 0.1f)
371
371
-
), DrawableObject(
372
372
-
obj = it,
373
373
-
shape = DrawableShape.RECTANGLE,
374
374
-
colour = Color.Green,
375
375
-
style = Stroke(it.boundingBox.width * 0.1f)
376
376
-
)
377
377
-
)
378
378
-
}
379
379
-
},
380
380
-
objectModel = generalModel,
381
381
-
modifier = Modifier.weight(1f),
382
382
-
focusArea = Rect(0f,0f,1f,1f),
383
383
-
frontCamera = frontCamera,
384
384
-
useUltraWide = ultrawide,
385
385
-
previewFillMode = previewFillMode,
386
386
-
recordingId = recordingId,
387
387
-
controller = controller,
388
388
-
onVideoSaved = { id, url -> path = url },
389
389
-
)
390
390
-
}
391
391
-
Button(
392
392
-
onClick = {
393
393
-
//recordingId = "${Clock.System.now().epochSeconds}"
394
394
-
//ultrawide = !ultrawide
395
395
-
previewFillMode = if(previewFillMode==PreviewFillMode.FIT) PreviewFillMode.FILL else PreviewFillMode.FIT
396
396
-
}, modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart)
397
397
-
) {
398
398
-
Text("Start Recording")
417
417
+
Box(
418
418
+
modifier = Modifier
419
419
+
.imePadding()
420
420
+
.padding(12.dp)
421
421
+
.align(Alignment.TopEnd)
422
422
+
) {
423
423
+
IconButton(onClick = { menuExpanded = true }) {
424
424
+
Text("⋮", color = Color.White, fontSize = 22.sp)
425
425
+
}
426
426
+
427
427
+
DropdownMenu(
428
428
+
expanded = menuExpanded,
429
429
+
onDismissRequest = { menuExpanded = false }
430
430
+
) {
431
431
+
// Zoom toggle
432
432
+
DropdownMenuItem(
433
433
+
text = {
434
434
+
Row {
435
435
+
Text(text = if(ultrawide) "0.5x zoom" else "1.0x zoom")
436
436
+
Spacer(Modifier.width(12.dp))
437
437
+
Switch(checked = ultrawide, onCheckedChange = null)
438
438
+
}
439
439
+
},
440
440
+
onClick = { ultrawide = !ultrawide }
441
441
+
)
442
442
+
443
443
+
// Preview fill/crop toggle
444
444
+
DropdownMenuItem(
445
445
+
text = {
446
446
+
Row {
447
447
+
Text(text = if(previewFillMode==PreviewFillMode.FIT)"Fill Preview" else "Fit Preview")
448
448
+
Spacer(Modifier.width(12.dp))
449
449
+
Switch(checked = previewFillMode==PreviewFillMode.FIT, onCheckedChange = null)
450
450
+
}
451
451
+
},
452
452
+
onClick = {
453
453
+
previewFillMode = if(previewFillMode==PreviewFillMode.FIT) PreviewFillMode.FILL else PreviewFillMode.FIT
454
454
+
}
455
455
+
)
456
456
+
457
457
+
HorizontalDivider()
458
458
+
459
459
+
// Start/End recording button
460
460
+
DropdownMenuItem(
461
461
+
text = { Text(if (recordingId!=null) "End recording" else "Start recording") },
462
462
+
onClick = {
463
463
+
if(recordingId == null){
464
464
+
recordingId = "${Clock.System.now().epochSeconds}"
465
465
+
}else{
466
466
+
recordingId = null
467
467
+
}
468
468
+
// Keep menu open or close? Close feels better.
469
469
+
menuExpanded = false
470
470
+
}
471
471
+
)
472
472
+
}
473
473
+
}
399
474
}
400
475
}
476
476
+
401
477
} else Text("Camera permission not granted")
478
478
+
402
479
val upRightPose = Pose(
403
480
leftShoulder = Pose.PoseRange(0.0, 40.0),
404
481
rightShoulder = Pose.PoseRange(0.0, 40.0),
···
1
1
+
package com.nate.posedetection
2
2
+
3
3
+
import androidx.compose.foundation.layout.BoxWithConstraints
4
4
+
import androidx.compose.runtime.Composable
5
5
+
import androidx.compose.runtime.mutableStateOf
6
6
+
import androidx.compose.runtime.remember
7
7
+
8
8
+
enum class Orientation {
9
9
+
PORTRAIT, LANDSCAPE
10
10
+
}
11
11
+
12
12
+
@Composable
13
13
+
fun DetectOrientation(content: @Composable (orientation: Orientation) -> Unit) {
14
14
+
BoxWithConstraints {
15
15
+
val orientation = if (maxWidth > maxHeight) Orientation.LANDSCAPE else Orientation.PORTRAIT
16
16
+
val currentOrientation = remember { mutableStateOf(orientation) }
17
17
+
18
18
+
// Update if orientation changes
19
19
+
if (currentOrientation.value != orientation) {
20
20
+
currentOrientation.value = orientation
21
21
+
}
22
22
+
23
23
+
content(currentOrientation.value)
24
24
+
}
25
25
+
}