p5-phone 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -24
- package/dist/p5-phone.js +196 -6
- package/dist/p5-phone.min.js +1 -1
- package/examples/Phone Sensor Examples/microphone/01_mic_level/index.html +1 -1
- package/examples/Phone Sensor Examples/microphone/02_speech_recognition/index.html +1 -1
- package/examples/Phone Sensor Examples/movement/01_orientation_basic/index.html +1 -1
- package/examples/Phone Sensor Examples/movement/02_rotational_velocity/index.html +1 -1
- package/examples/Phone Sensor Examples/movement/03_acceleration/index.html +1 -1
- package/examples/Phone Sensor Examples/movement/04_device_shaken/index.html +23 -0
- package/examples/Phone Sensor Examples/movement/04_device_shaken/sketch.js +146 -0
- package/examples/Phone Sensor Examples/movement/05_device_moved/index.html +23 -0
- package/examples/Phone Sensor Examples/movement/05_device_moved/sketch.js +175 -0
- package/examples/Phone Sensor Examples/movement/06_device_orientation/index.html +23 -0
- package/examples/Phone Sensor Examples/movement/06_device_orientation/sketch.js +120 -0
- package/examples/Phone Sensor Examples/nfc/01_nfc_read/index.html +3 -3
- package/examples/Phone Sensor Examples/nfc/01_nfc_read/sketch.js +252 -32
- package/examples/Phone Sensor Examples/nfc/02_two_tag_effects/index.html +25 -0
- package/examples/Phone Sensor Examples/nfc/02_two_tag_effects/sketch.js +208 -0
- package/examples/Phone Sensor Examples/sound/01_dual_audio/index.html +1 -1
- package/examples/Phone Sensor Examples/sound/01_dual_audio/sketch.js +52 -54
- package/examples/Phone Sensor Examples/sound/02_volume_touches/index.html +1 -1
- package/examples/Phone Sensor Examples/sound/02_volume_touches/sketch.js +66 -67
- package/examples/Phone Sensor Examples/sound/03_motion_synth/index.html +24 -0
- package/examples/Phone Sensor Examples/sound/03_motion_synth/sketch.js +172 -0
- package/examples/Phone Sensor Examples/touch/01_touch_basic/index.html +1 -1
- package/examples/Phone Sensor Examples/touch/02_touch_zones/index.html +1 -1
- package/examples/Phone Sensor Examples/touch/03_touch_count/index.html +1 -1
- package/examples/Phone Sensor Examples/touch/04_touch_distance/index.html +1 -1
- package/examples/Phone Sensor Examples/touch/05_touch_angle/index.html +1 -1
- package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/index.html +1 -1
- package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/sketch.js +72 -74
- package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/movement/04_device_shaken/index.html +18 -0
- package/examples/Phone Sensor Examples - Minimal/movement/04_device_shaken/sketch.js +46 -0
- package/examples/Phone Sensor Examples - Minimal/movement/05_device_moved/index.html +18 -0
- package/examples/Phone Sensor Examples - Minimal/movement/05_device_moved/sketch.js +46 -0
- package/examples/Phone Sensor Examples - Minimal/movement/06_device_orientation/index.html +18 -0
- package/examples/Phone Sensor Examples - Minimal/movement/06_device_orientation/sketch.js +40 -0
- package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/sound/03_motion_synth/index.html +19 -0
- package/examples/Phone Sensor Examples - Minimal/sound/03_motion_synth/sketch.js +63 -0
- package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/vibration/01_haptic_feedback/index.html +1 -1
- package/examples/Phone Sensor Examples - Minimal/vibration/01_haptic_feedback/sketch.js +45 -44
- package/examples/Phone and Gif/collision/index.html +1 -1
- package/examples/Phone and Gif/fetch/index.html +1 -1
- package/examples/Phone and Gif/fly/index.html +1 -1
- package/examples/Phone and Gif/roll/index.html +1 -1
- package/examples/UIStyles/canvas-style/sketch.js +13 -13
- package/examples/UIStyles/custom-element/sketch.js +19 -20
- package/examples/UXcompare/button-vs-movement/index.html +1 -1
- package/examples/UXcompare/button-vs-orientation/index.html +1 -1
- package/examples/UXcompare/button-vs-shake/index.html +1 -1
- package/examples/UXcompare/gyroscope-demo/index.html +1 -1
- package/examples/UXcompare/microphone-demo/index.html +1 -1
- package/examples/UXcompare/slider-vs-angle/index.html +1 -1
- package/examples/UXcompare/slider-vs-distance/index.html +1 -1
- package/examples/UXcompare/slider-vs-microphone/index.html +1 -1
- package/examples/UXcompare/slider-vs-touches/index.html +1 -1
- package/examples/UXcompare/sliders-vs-acceleration/index.html +1 -1
- package/examples/UXcompare/sliders-vs-rotation/index.html +1 -1
- package/examples/blankTemplate/index.html +1 -1
- package/examples/homepage/index.html +105 -6
- package/examples/homepage-v2/index.html +263 -0
- package/examples/homepage-v2/scripts/api-data.js +97 -0
- package/examples/homepage-v2/scripts/examples-data.js +463 -0
- package/examples/homepage-v2/scripts/navigation.js +45 -0
- package/examples/homepage-v2/scripts/render.js +214 -0
- package/examples/homepage-v2/styles/main.css +119 -0
- package/examples/ml5/Gaze_detector_class/GazeDetector.js +1 -1
- package/examples/ml5/Gaze_detector_class/README.md +1 -1
- package/examples/ml5/Gaze_detector_class/index.html +1 -1
- package/examples/ml5/PHONE_BodyPose_two_points/index.html +1 -1
- package/examples/ml5/PHONE_BodyPose_two_points/sketch.js +1 -1
- package/examples/ml5/PHONE_FaceMesh_two_points/index.html +1 -1
- package/examples/ml5/PHONE_FaceMesh_two_points/sketch.js +1 -1
- package/examples/ml5/PHONE_HandPose_two_points/index.html +1 -1
- package/examples/ml5/PHONE_HandPose_two_points/sketch.js +1 -1
- package/examples/ml5/PHONE_ObjectDetection/index.html +27 -0
- package/examples/ml5/PHONE_ObjectDetection/sketch.js +236 -0
- package/examples/ml5/THREE_FaceMesh_two_points/sketch.js +1 -1
- package/examples/ml5/THREE_HandPose_two_points/sketch.js +1 -1
- package/package.json +1 -1
- package/src/p5-phone.js +196 -6
package/README.md
CHANGED
|
@@ -102,10 +102,10 @@ p5-phone automatically detects the p5.js version and adjusts its internal touch
|
|
|
102
102
|
|
|
103
103
|
```html
|
|
104
104
|
<!-- Minified version (recommended) -->
|
|
105
|
-
<script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.
|
|
105
|
+
<script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
|
|
106
106
|
|
|
107
107
|
<!-- Development version (larger, with comments) -->
|
|
108
|
-
<!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.
|
|
108
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script> -->
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
### Basic Setup
|
|
@@ -134,7 +134,7 @@ p5-phone automatically detects the p5.js version and adjusts its internal touch
|
|
|
134
134
|
<!-- For p5.js 2.0: <script src="https://cdn.jsdelivr.net/npm/p5@2/lib/p5.min.js"></script> -->
|
|
135
135
|
|
|
136
136
|
<!-- Load p5-phone library -->
|
|
137
|
-
<script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.
|
|
137
|
+
<script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
|
|
138
138
|
|
|
139
139
|
</head>
|
|
140
140
|
<body>
|
|
@@ -305,6 +305,8 @@ this.enableGyroTap('Tap to start');
|
|
|
305
305
|
- `window.speechEnabled` - Boolean indicating if speech recognition is active
|
|
306
306
|
- `window.vibrationEnabled` - Boolean indicating if vibration is available (Android only)
|
|
307
307
|
- `window.nfcEnabled` - Boolean indicating if NFC scanning is active (Android only)
|
|
308
|
+
- `window.lastNfcSerialNumber` - Serial number string for the most recently read NFC tag
|
|
309
|
+
- `window.lastNfcAlias` - Alias string for the most recently read NFC tag, if one has been set
|
|
308
310
|
|
|
309
311
|
**Usage:**
|
|
310
312
|
```javascript
|
|
@@ -337,11 +339,10 @@ function draw() {
|
|
|
337
339
|
}
|
|
338
340
|
|
|
339
341
|
// You can also use them for conditional UI
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (!window.sensorsEnabled) {
|
|
342
|
+
function draw() {
|
|
343
|
+
if (window.sensorsEnabled) {
|
|
344
|
+
debug("Motion sensors enabled");
|
|
345
|
+
} else {
|
|
345
346
|
debug("Motion sensors not yet enabled");
|
|
346
347
|
}
|
|
347
348
|
}
|
|
@@ -661,11 +662,23 @@ function gameOver() {
|
|
|
661
662
|
- `enableNfcTap(message)` - Tap anywhere on screen to enable NFC scanning
|
|
662
663
|
- `enableNfcButton(text)` - Creates a button with custom text to enable NFC
|
|
663
664
|
- `stopNfc()` - Stop NFC scanning
|
|
665
|
+
- `setNfcTagAlias(serialNumber, alias)` - Give a tag ID a human-friendly name
|
|
666
|
+
- `getNfcTagAlias(serialNumber)` - Get a saved alias for a tag ID
|
|
667
|
+
- `isNfcTag(aliasOrSerialNumber)` - Check whether the most recently read tag matches an alias or ID
|
|
664
668
|
|
|
665
669
|
**Status Variables:**
|
|
666
670
|
- `window.nfcEnabled` - Boolean indicating if NFC scanning is active
|
|
671
|
+
- `window.nfcStatus` - String describing NFC startup/read status
|
|
672
|
+
- `window.nfcError` - String containing the latest NFC startup/read error
|
|
673
|
+
- `window.nfcTagAliases` - Object mapping tag IDs to aliases
|
|
667
674
|
- `window.lastNfcMessage` - Object containing the most recently read tag's data
|
|
668
675
|
- `window.lastNfcSerialNumber` - Serial number string of the most recently read tag
|
|
676
|
+
- `window.lastNfcAlias` - Alias string for the most recently read tag, if one has been set
|
|
677
|
+
|
|
678
|
+
**Two-step workflow:**
|
|
679
|
+
|
|
680
|
+
1. Open the NFC identifier example, scan each physical tag, type an alias, then download the tag list.
|
|
681
|
+
2. Paste the generated `setNfcTagAlias()` lines into your sketch and use `isNfcTag()` in conditionals.
|
|
669
682
|
|
|
670
683
|
**User Callback:**
|
|
671
684
|
|
|
@@ -674,6 +687,7 @@ Define an `nfcRead()` function in your sketch to receive tag data when a tag is
|
|
|
674
687
|
```javascript
|
|
675
688
|
function nfcRead(message, serialNumber) {
|
|
676
689
|
// message.serialNumber — tag serial number
|
|
690
|
+
// message.alias — saved alias, or '' if unnamed
|
|
677
691
|
// message.records — array of NDEF records, each with:
|
|
678
692
|
// .recordType — 'text', 'url', 'mime', etc.
|
|
679
693
|
// .data — decoded content (string for text/url, object for JSON, raw for others)
|
|
@@ -690,6 +704,11 @@ let tagText = 'No tag scanned yet';
|
|
|
690
704
|
function setup() {
|
|
691
705
|
createCanvas(windowWidth, windowHeight);
|
|
692
706
|
lockGestures();
|
|
707
|
+
|
|
708
|
+
// Paste IDs from the NFC identifier example.
|
|
709
|
+
setNfcTagAlias('04:85:2a:1b:9f:61:80', 'paintbrush');
|
|
710
|
+
setNfcTagAlias('04:42:18:3c:9f:61:80', 'desk');
|
|
711
|
+
|
|
693
712
|
enableNfcTap('Tap to enable NFC');
|
|
694
713
|
}
|
|
695
714
|
|
|
@@ -698,16 +717,29 @@ function draw() {
|
|
|
698
717
|
textAlign(CENTER, CENTER);
|
|
699
718
|
textSize(20);
|
|
700
719
|
|
|
701
|
-
if (
|
|
702
|
-
|
|
720
|
+
if (window.nfcEnabled) {
|
|
721
|
+
if (isNfcTag('paintbrush')) {
|
|
722
|
+
background(120, 220, 160);
|
|
723
|
+
text('Paintbrush tag read', width / 2, height / 2);
|
|
724
|
+
} else if (isNfcTag('desk')) {
|
|
725
|
+
background(140, 180, 240);
|
|
726
|
+
text('Desk tag read', width / 2, height / 2);
|
|
727
|
+
} else {
|
|
728
|
+
text(tagText, width / 2, height / 2);
|
|
729
|
+
text('Hold an NFC tag near your phone', width / 2, height / 2 + 40);
|
|
730
|
+
}
|
|
703
731
|
} else {
|
|
704
|
-
text(
|
|
705
|
-
text('Hold an NFC tag near your phone', width / 2, height / 2 + 40);
|
|
732
|
+
text('NFC not active', width / 2, height / 2);
|
|
706
733
|
}
|
|
707
734
|
}
|
|
708
735
|
|
|
709
736
|
function nfcRead(message, serialNumber) {
|
|
710
|
-
tagText = 'Tag: ' + serialNumber;
|
|
737
|
+
tagText = 'Tag: ' + (message.alias || serialNumber);
|
|
738
|
+
|
|
739
|
+
if (isNfcTag('paintbrush', serialNumber)) {
|
|
740
|
+
// This runs once when the paintbrush tag is scanned.
|
|
741
|
+
}
|
|
742
|
+
|
|
711
743
|
for (let record of message.records) {
|
|
712
744
|
if (record.recordType === 'text' || record.recordType === 'url') {
|
|
713
745
|
tagText += '\n' + record.data;
|
|
@@ -730,6 +762,7 @@ NFC tags contain NDEF records. The most common types are:
|
|
|
730
762
|
**Best Practices:**
|
|
731
763
|
- Always check `window.nfcEnabled` before relying on NFC features
|
|
732
764
|
- Use the `nfcRead()` callback for real-time tag processing
|
|
765
|
+
- Use `setNfcTagAlias()` and `isNfcTag()` when a sketch should respond to named physical objects
|
|
733
766
|
- Use `window.lastNfcMessage` in `draw()` for displaying the most recent tag
|
|
734
767
|
- Test on Android devices with Chrome — NFC is not available on iOS or desktop browsers
|
|
735
768
|
- Tags must be NDEF-formatted to be read by the Web NFC API
|
|
@@ -810,10 +843,10 @@ function draw() {
|
|
|
810
843
|
|
|
811
844
|
### PhoneCamera (ML5 Integration)
|
|
812
845
|
|
|
813
|
-
**Purpose:** Simplified camera access optimized for ML5.js machine learning models (FaceMesh, HandPose, BodyPose, etc.). Handles camera initialization, coordinate mapping, mirroring, and display modes automatically.
|
|
846
|
+
**Purpose:** Simplified camera access optimized for ML5.js machine learning models (FaceMesh, HandPose, BodyPose, ObjectDetection, etc.). Handles camera initialization, coordinate mapping, mirroring, and display modes automatically.
|
|
814
847
|
|
|
815
848
|
**Key Features:**
|
|
816
|
-
- **Automatic Coordinate Mapping** - ML5 keypoints automatically mapped to canvas coordinates
|
|
849
|
+
- **Automatic Coordinate Mapping** - ML5 keypoints and bounding boxes automatically mapped to canvas coordinates
|
|
817
850
|
- **Mirror Support** - Handles front camera mirroring for natural interaction
|
|
818
851
|
- **Display Modes** - Multiple video sizing options (fitHeight, cover, contain, fixed)
|
|
819
852
|
- **ML5 Optimized** - Direct integration with ML5 v1.x models
|
|
@@ -828,6 +861,8 @@ function draw() {
|
|
|
828
861
|
| `cam.onReady(callback)` | Execute code when camera ready | Callback function |
|
|
829
862
|
| `cam.mapKeypoint(keypoint)` | Map single ML5 keypoint to screen | ML5 keypoint object |
|
|
830
863
|
| `cam.mapKeypoints(keypoints)` | Map array of ML5 keypoints | Array of ML5 keypoints |
|
|
864
|
+
| `cam.mapBox(box)` | Map single ML5 bounding box to screen | ML5 detection object with x, y, width, height |
|
|
865
|
+
| `cam.mapBoxes(boxes)` | Map array of ML5 bounding boxes | Array of ML5 detection objects |
|
|
831
866
|
|
|
832
867
|
**Properties:**
|
|
833
868
|
|
|
@@ -861,7 +896,7 @@ function setup() {
|
|
|
861
896
|
let options = {
|
|
862
897
|
maxFaces: 1,
|
|
863
898
|
refineLandmarks: false,
|
|
864
|
-
|
|
899
|
+
flipped: false // cam.mapKeypoint() handles mirroring
|
|
865
900
|
};
|
|
866
901
|
|
|
867
902
|
facemesh = ml5.faceMesh(options, modelLoaded);
|
|
@@ -914,7 +949,7 @@ function draw() {
|
|
|
914
949
|
|
|
915
950
|
**Coordinate Mapping:**
|
|
916
951
|
|
|
917
|
-
The `mapKeypoint()` and `
|
|
952
|
+
The `mapKeypoint()`, `mapKeypoints()`, `mapBox()`, and `mapBoxes()` functions automatically handle:
|
|
918
953
|
- Video-to-canvas scaling
|
|
919
954
|
- Mirror transformation (for front camera)
|
|
920
955
|
- Offset positioning (for different display modes)
|
|
@@ -930,26 +965,34 @@ let hands = cam.mapKeypoints(hand.keypoints);
|
|
|
930
965
|
hands.forEach(point => {
|
|
931
966
|
circle(point.x, point.y, 5);
|
|
932
967
|
});
|
|
968
|
+
|
|
969
|
+
// Object detection bounding box
|
|
970
|
+
let mappedObject = cam.mapBox(detection);
|
|
971
|
+
rect(mappedObject.x, mappedObject.y, mappedObject.width, mappedObject.height);
|
|
933
972
|
```
|
|
934
973
|
|
|
935
974
|
**ML5 Model Examples:**
|
|
936
975
|
|
|
937
976
|
```javascript
|
|
938
977
|
// FaceMesh (468 keypoints)
|
|
939
|
-
let options = { maxFaces: 1, refineLandmarks: false,
|
|
978
|
+
let options = { maxFaces: 1, refineLandmarks: false, flipped: false };
|
|
940
979
|
facemesh = ml5.faceMesh(options, modelLoaded);
|
|
941
980
|
|
|
942
981
|
// HandPose (21 keypoints per hand)
|
|
943
|
-
let options = { maxHands: 2, runtime: 'mediapipe',
|
|
982
|
+
let options = { maxHands: 2, runtime: 'mediapipe', flipped: false };
|
|
944
983
|
handpose = ml5.handPose(options, modelLoaded);
|
|
945
984
|
|
|
946
985
|
// BodyPose (33 keypoints with 3D)
|
|
947
986
|
let options = { modelType: 'MULTIPOSE_LIGHTNING', flipped: false };
|
|
948
987
|
bodypose = ml5.bodyPose('BlazePose', options, modelLoaded);
|
|
988
|
+
|
|
989
|
+
// ObjectDetection (bounding boxes)
|
|
990
|
+
objectDetector = await ml5.objectDetection('cocossd');
|
|
991
|
+
objectDetector.detectStart(cam.videoElement, gotDetections);
|
|
949
992
|
```
|
|
950
993
|
|
|
951
994
|
**Important Notes:**
|
|
952
|
-
- Always set `
|
|
995
|
+
- Always set `flipped: false` in ML5 options when available (PhoneCamera handles mirroring)
|
|
953
996
|
- Use `cam.videoElement` (native HTML video element) when passing to ML5's `detectStart()`
|
|
954
997
|
- Check `cam.ready` before using video or drawing keypoints
|
|
955
998
|
- Call `enableCameraTap()` to handle camera permissions automatically
|
|
@@ -1033,9 +1076,11 @@ function setup() {
|
|
|
1033
1076
|
}
|
|
1034
1077
|
|
|
1035
1078
|
function draw() {
|
|
1036
|
-
if (!window.sensorsEnabled) return;
|
|
1037
1079
|
background(220);
|
|
1038
|
-
|
|
1080
|
+
|
|
1081
|
+
if (window.sensorsEnabled) {
|
|
1082
|
+
circle(width/2 + rotationY * 3, height/2 + rotationX * 3, 50);
|
|
1083
|
+
}
|
|
1039
1084
|
}
|
|
1040
1085
|
```
|
|
1041
1086
|
|
|
@@ -1095,9 +1140,11 @@ function setup() {
|
|
|
1095
1140
|
}
|
|
1096
1141
|
|
|
1097
1142
|
function draw() {
|
|
1098
|
-
if (!window.sensorsEnabled) return;
|
|
1099
1143
|
background(220);
|
|
1100
|
-
|
|
1144
|
+
|
|
1145
|
+
if (window.sensorsEnabled) {
|
|
1146
|
+
circle(width/2 + rotationY * 3, height/2 + rotationX * 3, 50);
|
|
1147
|
+
}
|
|
1101
1148
|
}
|
|
1102
1149
|
```
|
|
1103
1150
|
|
package/dist/p5-phone.js
CHANGED
|
@@ -88,8 +88,12 @@ window.gesturesLocked = false;
|
|
|
88
88
|
window.vibrationEnabled = false;
|
|
89
89
|
window.speechEnabled = false;
|
|
90
90
|
window.nfcEnabled = false;
|
|
91
|
+
window.nfcError = '';
|
|
92
|
+
window.nfcStatus = 'idle';
|
|
93
|
+
window.nfcTagAliases = {};
|
|
91
94
|
window.lastNfcMessage = null;
|
|
92
95
|
window.lastNfcSerialNumber = null;
|
|
96
|
+
window.lastNfcAlias = '';
|
|
93
97
|
|
|
94
98
|
// Internal state
|
|
95
99
|
let _micInstance = null;
|
|
@@ -557,9 +561,80 @@ function stopNfc() {
|
|
|
557
561
|
}
|
|
558
562
|
_nfcReader = null;
|
|
559
563
|
window.nfcEnabled = false;
|
|
564
|
+
window.nfcStatus = 'stopped';
|
|
560
565
|
console.log('NFC scanning stopped');
|
|
561
566
|
}
|
|
562
567
|
|
|
568
|
+
function _normalizeNfcText(value) {
|
|
569
|
+
return value == null ? '' : String(value).trim();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function _normalizeNfcTagId(serialNumber) {
|
|
573
|
+
return _normalizeNfcText(serialNumber).toLowerCase();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function _nfcTextMatches(firstValue, secondValue) {
|
|
577
|
+
const firstText = _normalizeNfcText(firstValue).toLowerCase();
|
|
578
|
+
const secondText = _normalizeNfcText(secondValue).toLowerCase();
|
|
579
|
+
return firstText !== '' && firstText === secondText;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Give an NFC tag a human-friendly alias.
|
|
584
|
+
* Pass an empty alias to remove the stored name for a tag.
|
|
585
|
+
*/
|
|
586
|
+
function setNfcTagAlias(serialNumber, alias) {
|
|
587
|
+
const tagId = _normalizeNfcTagId(serialNumber);
|
|
588
|
+
const tagAlias = _normalizeNfcText(alias);
|
|
589
|
+
|
|
590
|
+
if (!tagId) {
|
|
591
|
+
console.warn('p5-phone: setNfcTagAlias() needs an NFC serial number');
|
|
592
|
+
return '';
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!tagAlias) {
|
|
596
|
+
delete window.nfcTagAliases[tagId];
|
|
597
|
+
} else {
|
|
598
|
+
window.nfcTagAliases[tagId] = tagAlias;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (_normalizeNfcTagId(window.lastNfcSerialNumber) === tagId) {
|
|
602
|
+
window.lastNfcAlias = tagAlias;
|
|
603
|
+
if (window.lastNfcMessage) {
|
|
604
|
+
window.lastNfcMessage.alias = tagAlias;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return tagAlias;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get the human-friendly alias for an NFC tag serial number.
|
|
613
|
+
*/
|
|
614
|
+
function getNfcTagAlias(serialNumber = window.lastNfcSerialNumber) {
|
|
615
|
+
const tagId = _normalizeNfcTagId(serialNumber);
|
|
616
|
+
return tagId ? (window.nfcTagAliases[tagId] || '') : '';
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Check whether the most recently read NFC tag matches an alias or serial number.
|
|
621
|
+
* Optionally pass a serial number from nfcRead(message, serialNumber) as the second argument.
|
|
622
|
+
*/
|
|
623
|
+
function isNfcTag(aliasOrSerialNumber, serialNumber = window.lastNfcSerialNumber) {
|
|
624
|
+
const tagId = _normalizeNfcTagId(serialNumber);
|
|
625
|
+
const targetText = _normalizeNfcText(aliasOrSerialNumber);
|
|
626
|
+
|
|
627
|
+
if (!tagId || !targetText) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (tagId === _normalizeNfcTagId(targetText)) {
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return _nfcTextMatches(getNfcTagAlias(serialNumber), targetText);
|
|
636
|
+
}
|
|
637
|
+
|
|
563
638
|
// =========================================
|
|
564
639
|
// INTERNAL PERMISSION HANDLERS
|
|
565
640
|
// =========================================
|
|
@@ -687,6 +762,13 @@ async function _requestVibrationPermissionCore() {
|
|
|
687
762
|
|
|
688
763
|
async function _requestNfcPermissionCore() {
|
|
689
764
|
try {
|
|
765
|
+
if (window.nfcEnabled && _nfcReader) {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
window.nfcError = '';
|
|
770
|
+
window.nfcStatus = 'starting';
|
|
771
|
+
|
|
690
772
|
// Check if Web NFC API is supported
|
|
691
773
|
if (!('NDEFReader' in window)) {
|
|
692
774
|
console.warn('⚠️ Web NFC API not supported on this device/browser (Android Chrome 89+ required)');
|
|
@@ -694,9 +776,14 @@ async function _requestNfcPermissionCore() {
|
|
|
694
776
|
debugWarn('Web NFC not supported on this device/browser');
|
|
695
777
|
}
|
|
696
778
|
window.nfcEnabled = false;
|
|
697
|
-
|
|
779
|
+
window.nfcStatus = 'unsupported';
|
|
780
|
+
window.nfcError = window.isSecureContext === false
|
|
781
|
+
? 'NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.'
|
|
782
|
+
: 'Web NFC is not supported in this browser. Use Android Chrome 89+ over HTTPS.';
|
|
783
|
+
return false;
|
|
698
784
|
}
|
|
699
785
|
|
|
786
|
+
window.nfcStatus = 'requesting-permission';
|
|
700
787
|
_nfcAbortController = new AbortController();
|
|
701
788
|
_nfcReader = new NDEFReader();
|
|
702
789
|
|
|
@@ -734,9 +821,13 @@ async function _requestNfcPermissionCore() {
|
|
|
734
821
|
records.push(entry);
|
|
735
822
|
}
|
|
736
823
|
|
|
737
|
-
const
|
|
824
|
+
const alias = getNfcTagAlias(serialNumber);
|
|
825
|
+
const message = { serialNumber: serialNumber, alias: alias, records: records };
|
|
738
826
|
window.lastNfcMessage = message;
|
|
739
827
|
window.lastNfcSerialNumber = serialNumber;
|
|
828
|
+
window.lastNfcAlias = alias;
|
|
829
|
+
window.nfcStatus = 'tag-read';
|
|
830
|
+
window.nfcError = '';
|
|
740
831
|
|
|
741
832
|
// Call user-defined callback if it exists
|
|
742
833
|
if (typeof nfcRead === 'function') {
|
|
@@ -751,6 +842,7 @@ async function _requestNfcPermissionCore() {
|
|
|
751
842
|
|
|
752
843
|
_nfcReader.onreadingerror = (event) => {
|
|
753
844
|
console.warn('⚠️ NFC read error — tag may be incompatible or out of range');
|
|
845
|
+
window.nfcError = 'NFC read error. Make sure the tag is NDEF formatted and hold it near the phone NFC antenna.';
|
|
754
846
|
if (_debugVisible) {
|
|
755
847
|
debugWarn('NFC read error — tag incompatible or out of range');
|
|
756
848
|
}
|
|
@@ -758,26 +850,44 @@ async function _requestNfcPermissionCore() {
|
|
|
758
850
|
|
|
759
851
|
await _nfcReader.scan({ signal: _nfcAbortController.signal });
|
|
760
852
|
window.nfcEnabled = true;
|
|
853
|
+
window.nfcStatus = 'scanning';
|
|
761
854
|
console.log('✅ NFC scanning active');
|
|
855
|
+
return true;
|
|
762
856
|
|
|
763
857
|
} catch (error) {
|
|
764
858
|
if (error.name === 'NotAllowedError') {
|
|
765
859
|
console.warn('⚠️ NFC permission denied by user');
|
|
860
|
+
window.nfcStatus = 'permission-denied';
|
|
861
|
+
window.nfcError = 'NFC permission was denied. Reload and tap Allow if Chrome asks.';
|
|
766
862
|
if (_debugVisible) {
|
|
767
863
|
debugWarn('NFC permission denied');
|
|
768
864
|
}
|
|
769
865
|
} else if (error.name === 'NotSupportedError') {
|
|
770
866
|
console.warn('⚠️ NFC not supported on this device');
|
|
867
|
+
window.nfcStatus = 'unsupported';
|
|
868
|
+
window.nfcError = 'NFC is not supported on this device/browser, or this page is not using HTTPS.';
|
|
771
869
|
if (_debugVisible) {
|
|
772
870
|
debugWarn('NFC not supported on this device');
|
|
773
871
|
}
|
|
872
|
+
} else if (error.name === 'SecurityError') {
|
|
873
|
+
console.warn('⚠️ NFC requires a secure HTTPS context');
|
|
874
|
+
window.nfcStatus = 'secure-context-required';
|
|
875
|
+
window.nfcError = 'NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.';
|
|
876
|
+
if (_debugVisible) {
|
|
877
|
+
debugWarn('NFC requires HTTPS');
|
|
878
|
+
}
|
|
774
879
|
} else {
|
|
775
880
|
console.error('NFC permission error:', error);
|
|
881
|
+
window.nfcStatus = 'error';
|
|
882
|
+
window.nfcError = error && error.message ? error.message : 'NFC could not start.';
|
|
776
883
|
if (_debugVisible) {
|
|
777
884
|
debugError('NFC error: ' + error.message);
|
|
778
885
|
}
|
|
779
886
|
}
|
|
780
887
|
window.nfcEnabled = false;
|
|
888
|
+
_nfcReader = null;
|
|
889
|
+
_nfcAbortController = null;
|
|
890
|
+
return false;
|
|
781
891
|
}
|
|
782
892
|
}
|
|
783
893
|
|
|
@@ -808,8 +918,9 @@ async function _requestVibrationPermission() {
|
|
|
808
918
|
}
|
|
809
919
|
|
|
810
920
|
async function _requestNfcPermission() {
|
|
811
|
-
await _requestNfcPermissionCore();
|
|
921
|
+
const enabled = await _requestNfcPermissionCore();
|
|
812
922
|
_notifySketchReady();
|
|
923
|
+
return enabled;
|
|
813
924
|
}
|
|
814
925
|
|
|
815
926
|
function _notifySketchReady() {
|
|
@@ -839,6 +950,7 @@ function _notifySketchReady() {
|
|
|
839
950
|
function _createPermissionButton(buttonText, statusText, onClickHandler) {
|
|
840
951
|
// Remove existing button if present
|
|
841
952
|
_removeExistingUI();
|
|
953
|
+
let activating = false;
|
|
842
954
|
|
|
843
955
|
// Create button
|
|
844
956
|
const button = document.createElement('button');
|
|
@@ -891,7 +1003,8 @@ function _createPermissionButton(buttonText, statusText, onClickHandler) {
|
|
|
891
1003
|
|
|
892
1004
|
// Add multiple event handlers to ensure responsiveness
|
|
893
1005
|
const handleButtonClick = async () => {
|
|
894
|
-
if (button.parentNode) {
|
|
1006
|
+
if (!activating && button.parentNode) {
|
|
1007
|
+
activating = true;
|
|
895
1008
|
button.style.display = 'none';
|
|
896
1009
|
status.style.display = 'block';
|
|
897
1010
|
|
|
@@ -922,6 +1035,7 @@ function _createPermissionButton(buttonText, statusText, onClickHandler) {
|
|
|
922
1035
|
function _createTapToEnable(message, onTapHandler) {
|
|
923
1036
|
// Remove existing UI if present
|
|
924
1037
|
_removeExistingUI();
|
|
1038
|
+
let activating = false;
|
|
925
1039
|
|
|
926
1040
|
// Create overlay
|
|
927
1041
|
const overlay = document.createElement('div');
|
|
@@ -960,7 +1074,8 @@ function _createTapToEnable(message, onTapHandler) {
|
|
|
960
1074
|
|
|
961
1075
|
// Add multiple event handlers to ensure responsiveness
|
|
962
1076
|
const handleActivation = async () => {
|
|
963
|
-
if (overlay.parentNode) {
|
|
1077
|
+
if (!activating && overlay.parentNode) {
|
|
1078
|
+
activating = true;
|
|
964
1079
|
messageDiv.textContent = 'Enabling...';
|
|
965
1080
|
await onTapHandler();
|
|
966
1081
|
if (overlay.parentNode) {
|
|
@@ -1074,6 +1189,7 @@ function _createCanvasToEnable(message, onActivateHandler) {
|
|
|
1074
1189
|
*/
|
|
1075
1190
|
function _createBannerToEnable(message, position, onActivateHandler) {
|
|
1076
1191
|
_removeExistingUI();
|
|
1192
|
+
let activating = false;
|
|
1077
1193
|
|
|
1078
1194
|
const banner = document.createElement('div');
|
|
1079
1195
|
banner.id = 'permissionBanner';
|
|
@@ -1110,7 +1226,8 @@ function _createBannerToEnable(message, position, onActivateHandler) {
|
|
|
1110
1226
|
});
|
|
1111
1227
|
|
|
1112
1228
|
const handleActivation = async () => {
|
|
1113
|
-
if (!banner.parentNode) return;
|
|
1229
|
+
if (activating || !banner.parentNode) return;
|
|
1230
|
+
activating = true;
|
|
1114
1231
|
|
|
1115
1232
|
banner.textContent = 'Enabling...';
|
|
1116
1233
|
banner.style.pointerEvents = 'none';
|
|
@@ -1574,6 +1691,9 @@ window.stopVibration = stopVibration;
|
|
|
1574
1691
|
window.enableNfcTap = enableNfcTap;
|
|
1575
1692
|
window.enableNfcButton = enableNfcButton;
|
|
1576
1693
|
window.stopNfc = stopNfc;
|
|
1694
|
+
window.setNfcTagAlias = setNfcTagAlias;
|
|
1695
|
+
window.getNfcTagAlias = getNfcTagAlias;
|
|
1696
|
+
window.isNfcTag = isNfcTag;
|
|
1577
1697
|
window.enableAllTap = enableAllTap;
|
|
1578
1698
|
window.enableAllButton = enableAllButton;
|
|
1579
1699
|
|
|
@@ -2177,6 +2297,70 @@ class PhoneCamera {
|
|
|
2177
2297
|
|
|
2178
2298
|
return keypoints.map(kp => this.mapKeypoint(kp));
|
|
2179
2299
|
}
|
|
2300
|
+
|
|
2301
|
+
/**
|
|
2302
|
+
* Map an ML5 bounding box object to display coordinates
|
|
2303
|
+
* Handles scaling and mirroring automatically
|
|
2304
|
+
* Preserves labels, confidence, and any other detection properties
|
|
2305
|
+
* @param {object} box - ML5 box/detection { x, y, width, height, ... }
|
|
2306
|
+
* @returns {object} - Box with mapped x, y, width, and height
|
|
2307
|
+
*/
|
|
2308
|
+
mapBox(box) {
|
|
2309
|
+
if (!box) {
|
|
2310
|
+
console.warn('PhoneCamera.mapBox: invalid box', box);
|
|
2311
|
+
return box;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
const boxX = typeof box.x !== 'undefined' ? box.x : box.xMin;
|
|
2315
|
+
const boxY = typeof box.y !== 'undefined' ? box.y : box.yMin;
|
|
2316
|
+
const boxWidth = typeof box.width !== 'undefined' ? box.width : box.xMax - box.xMin;
|
|
2317
|
+
const boxHeight = typeof box.height !== 'undefined' ? box.height : box.yMax - box.yMin;
|
|
2318
|
+
const numericX = Number(boxX);
|
|
2319
|
+
const numericY = Number(boxY);
|
|
2320
|
+
const numericWidth = Number(boxWidth);
|
|
2321
|
+
const numericHeight = Number(boxHeight);
|
|
2322
|
+
|
|
2323
|
+
if (!Number.isFinite(numericX) ||
|
|
2324
|
+
!Number.isFinite(numericY) ||
|
|
2325
|
+
!Number.isFinite(numericWidth) ||
|
|
2326
|
+
!Number.isFinite(numericHeight)) {
|
|
2327
|
+
console.warn('PhoneCamera.mapBox: invalid box', box);
|
|
2328
|
+
return box;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
const topLeft = this.mapPoint(numericX, numericY);
|
|
2332
|
+
const bottomRight = this.mapPoint(numericX + numericWidth, numericY + numericHeight);
|
|
2333
|
+
const mappedX = Math.min(topLeft.x, bottomRight.x);
|
|
2334
|
+
const mappedY = Math.min(topLeft.y, bottomRight.y);
|
|
2335
|
+
const mappedWidth = Math.abs(bottomRight.x - topLeft.x);
|
|
2336
|
+
const mappedHeight = Math.abs(bottomRight.y - topLeft.y);
|
|
2337
|
+
|
|
2338
|
+
return {
|
|
2339
|
+
...box,
|
|
2340
|
+
x: mappedX,
|
|
2341
|
+
y: mappedY,
|
|
2342
|
+
width: mappedWidth,
|
|
2343
|
+
height: mappedHeight,
|
|
2344
|
+
xMin: mappedX,
|
|
2345
|
+
yMin: mappedY,
|
|
2346
|
+
xMax: mappedX + mappedWidth,
|
|
2347
|
+
yMax: mappedY + mappedHeight
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* Map an array of ML5 bounding boxes to display coordinates
|
|
2353
|
+
* @param {array} boxes - Array of ML5 boxes/detections
|
|
2354
|
+
* @returns {array} - Array of boxes with mapped coordinates
|
|
2355
|
+
*/
|
|
2356
|
+
mapBoxes(boxes) {
|
|
2357
|
+
if (!Array.isArray(boxes)) {
|
|
2358
|
+
console.warn('PhoneCamera.mapBoxes: expected array, got', typeof boxes);
|
|
2359
|
+
return boxes;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
return boxes.map(box => this.mapBox(box));
|
|
2363
|
+
}
|
|
2180
2364
|
|
|
2181
2365
|
// ========================================
|
|
2182
2366
|
// CANVAS DRAWING INTEGRATION
|
|
@@ -2362,6 +2546,9 @@ if (typeof p5 !== 'undefined' && p5.prototype) {
|
|
|
2362
2546
|
p5.prototype.enableNfcTap = enableNfcTap;
|
|
2363
2547
|
p5.prototype.enableNfcButton = enableNfcButton;
|
|
2364
2548
|
p5.prototype.stopNfc = stopNfc;
|
|
2549
|
+
p5.prototype.setNfcTagAlias = setNfcTagAlias;
|
|
2550
|
+
p5.prototype.getNfcTagAlias = getNfcTagAlias;
|
|
2551
|
+
p5.prototype.isNfcTag = isNfcTag;
|
|
2365
2552
|
p5.prototype.enableAllTap = enableAllTap;
|
|
2366
2553
|
p5.prototype.enableAllButton = enableAllButton;
|
|
2367
2554
|
|
|
@@ -2440,6 +2627,9 @@ if (typeof p5 !== 'undefined' && typeof p5.registerAddon === 'function') {
|
|
|
2440
2627
|
fn.enableNfcTap = enableNfcTap;
|
|
2441
2628
|
fn.enableNfcButton = enableNfcButton;
|
|
2442
2629
|
fn.stopNfc = stopNfc;
|
|
2630
|
+
fn.setNfcTagAlias = setNfcTagAlias;
|
|
2631
|
+
fn.getNfcTagAlias = getNfcTagAlias;
|
|
2632
|
+
fn.isNfcTag = isNfcTag;
|
|
2443
2633
|
fn.enableAllTap = enableAllTap;
|
|
2444
2634
|
fn.enableAllButton = enableAllButton;
|
|
2445
2635
|
|