From 76787aac123430326f3280fe2e6c980dbda28234 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 6 Nov 2020 16:36:33 +0100 Subject: [PATCH 01/67] First suggestion for camera platform interface --- .../camera_platform_interface/CHANGELOG.md | 1 + .../coverage/html/amber.png | Bin 0 -> 141 bytes .../coverage/html/emerald.png | Bin 0 -> 141 bytes .../coverage/html/gcov.css | 519 ++++++++++++++++++ .../coverage/html/glass.png | Bin 0 -> 167 bytes .../coverage/html/index-sort-f.html | 103 ++++ .../coverage/html/index-sort-l.html | 103 ++++ .../coverage/html/index.html | 103 ++++ .../camera_platform.dart.func-sort-c.html | 72 +++ .../camera_platform.dart.func.html | 72 +++ .../camera_platform.dart.gcov.html | 191 +++++++ .../html/platform_interface/index-sort-f.html | 93 ++++ .../html/platform_interface/index-sort-l.html | 93 ++++ .../html/platform_interface/index.html | 93 ++++ .../coverage/html/ruby.png | Bin 0 -> 141 bytes .../coverage/html/snow.png | Bin 0 -> 141 bytes .../camera_description.dart.func-sort-c.html | 72 +++ .../types/camera_description.dart.func.html | 72 +++ .../types/camera_description.dart.gcov.html | 131 +++++ .../types/camera_event.dart.func-sort-c.html | 72 +++ .../html/types/camera_event.dart.func.html | 72 +++ .../html/types/camera_event.dart.gcov.html | 152 +++++ .../types/camera_image.dart.func-sort-c.html | 72 +++ .../html/types/camera_image.dart.func.html | 72 +++ .../html/types/camera_image.dart.gcov.html | 224 ++++++++ .../coverage/html/types/index-sort-f.html | 123 +++++ .../coverage/html/types/index-sort-l.html | 123 +++++ .../coverage/html/types/index.html | 123 +++++ .../resolution_preset.dart.func-sort-c.html | 72 +++ .../types/resolution_preset.dart.func.html | 72 +++ .../types/resolution_preset.dart.gcov.html | 98 ++++ .../coverage/html/updown.png | Bin 0 -> 117 bytes .../coverage/lcov.info | 110 ++++ .../lib/src/types/callbacks.dart | 11 + .../lib/src/types/camera_event.dart | 76 +++ .../lib/src/types/camera_image.dart | 148 +++++ .../test/types/camera_image_test.dart | 113 ++++ 37 files changed, 3451 insertions(+) create mode 100644 packages/camera/camera_platform_interface/coverage/html/amber.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/emerald.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/gcov.css create mode 100644 packages/camera/camera_platform_interface/coverage/html/glass.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/ruby.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/snow.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/updown.png create mode 100644 packages/camera/camera_platform_interface/coverage/lcov.info create mode 100644 packages/camera/camera_platform_interface/lib/src/types/callbacks.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_event.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_image.dart create mode 100644 packages/camera/camera_platform_interface/test/types/camera_image_test.dart diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 500e8d67a6e4..2d26f8bcca91 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -13,3 +13,4 @@ ## 1.0.0 - Initial open-source release + First suggestion for camera platform interface diff --git a/packages/camera/camera_platform_interface/coverage/html/amber.png b/packages/camera/camera_platform_interface/coverage/html/amber.png new file mode 100644 index 0000000000000000000000000000000000000000..2cab170d8359081983a4e343848dfe06bc490f12 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/gcov.css b/packages/camera/camera_platform_interface/coverage/html/gcov.css new file mode 100644 index 000000000000..bfd0a83e10b5 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/gcov.css @@ -0,0 +1,519 @@ +/* All views: initial background and text color */ +body +{ + color: #000000; + background-color: #FFFFFF; +} + +/* All views: standard link format*/ +a:link +{ + color: #284FA8; + text-decoration: underline; +} + +/* All views: standard link - visited format */ +a:visited +{ + color: #00CB40; + text-decoration: underline; +} + +/* All views: standard link - activated format */ +a:active +{ + color: #FF0040; + text-decoration: underline; +} + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} + +/* All views: header item format */ +td.headerItem +{ + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; +} + +/* All views: header item value format */ +td.headerValue +{ + text-align: left; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; +} + +/* All views: header item coverage table heading */ +td.headerCovTableHead +{ + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; +} + +/* All views: header item coverage table entry */ +td.headerCovTableEntry +{ + text-align: right; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #DAE7FE; +} + +/* All views: header item coverage table entry for high coverage rate */ +td.headerCovTableEntryHi +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #A7FC9D; +} + +/* All views: header item coverage table entry for medium coverage rate */ +td.headerCovTableEntryMed +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FFEA20; +} + +/* All views: header item coverage table entry for ow coverage rate */ +td.headerCovTableEntryLo +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FF0000; +} + +/* All views: header legend value for legend entry */ +td.headerValueLeg +{ + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; +} + +/* All views: color of horizontal ruler */ +td.ruler +{ + background-color: #6688D4; +} + +/* All views: version string format */ +td.versionInfo +{ + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all)/Test case descriptions: + table headline format */ +td.tableHead +{ + text-align: center; + color: #FFFFFF; + background-color: #6688D4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; +} + +span.tableHeadSort +{ + padding-right: 4px; +} + +/* Directory view/File view (all): filename entry format */ +td.coverFile +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; +} + +/* Directory view/File view (all): bar-graph outline color */ +td.coverBarOutline +{ + background-color: #000000; +} + +/* Directory view/File view (all): percentage entry for files with + high coverage rate */ +td.coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + high coverage rate */ +td.coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + medium coverage rate */ +td.coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + medium coverage rate */ +td.coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + low coverage rate */ +td.coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + low coverage rate */ +td.coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + white-space: nowrap; + font-family: sans-serif; +} + +/* File view (all): "show/hide details" link format */ +a.detail:link +{ + color: #B8D0FF; + font-size:80%; +} + +/* File view (all): "show/hide details" link - visited format */ +a.detail:visited +{ + color: #B8D0FF; + font-size:80%; +} + +/* File view (all): "show/hide details" link - activated format */ +a.detail:active +{ + color: #FFFFFF; + font-size:80%; +} + +/* File view (detail): test name entry */ +td.testName +{ + text-align: right; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* File view (detail): test percentage entry */ +td.testPer +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* File view (detail): test lines count entry */ +td.testNum +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* Test case descriptions: test name format*/ +dt +{ + font-family: sans-serif; + font-weight: bold; +} + +/* Test case descriptions: description table body */ +td.testDescription +{ + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #DAE7FE; +} + +/* Source code view: function entry */ +td.coverFn +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: source code format */ +pre.source +{ + font-family: monospace; + white-space: pre; + margin-top: 2px; +} + +/* Source code view: line number format */ +span.lineNum +{ + background-color: #EFE383; +} + +/* Source code view: format for lines which were executed */ +td.lineCov, +span.lineCov +{ + background-color: #CAD7FE; +} + +/* Source code view: format for Cov legend */ +span.coverLegendCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #CAD7FE; +} + +/* Source code view: format for lines which were not executed */ +td.lineNoCov, +span.lineNoCov +{ + background-color: #FF6230; +} + +/* Source code view: format for NoCov legend */ +span.coverLegendNoCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #FF6230; +} + +/* Source code view (function table): standard link - visited format */ +td.lineNoCov > a:visited, +td.lineCov > a:visited +{ + color: black; + text-decoration: underline; +} + +/* Source code view: format for lines which were executed only in a + previous version */ +span.lineDiffCov +{ + background-color: #B5F7AF; +} + +/* Source code view: format for branches which were executed + * and taken */ +span.branchCov +{ + background-color: #CAD7FE; +} + +/* Source code view: format for branches which were executed + * but not taken */ +span.branchNoCov +{ + background-color: #FF6230; +} + +/* Source code view: format for branches which were not executed */ +span.branchNoExec +{ + background-color: #FF6230; +} + +/* Source code view: format for the source code heading line */ +pre.sourceHeading +{ + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; +} + +/* All views: header legend value for low rate */ +td.headerValueLegL +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #FF0000; + font-size: 80%; +} + +/* All views: header legend value for med rate */ +td.headerValueLegM +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #FFEA20; + font-size: 80%; +} + +/* All views: header legend value for hi rate */ +td.headerValueLegH +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #A7FC9D; + font-size: 80%; +} + +/* All views except source code view: legend format for low coverage */ +span.coverLegendCovLo +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FF0000; +} + +/* All views except source code view: legend format for med coverage */ +span.coverLegendCovMed +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FFEA20; +} + +/* All views except source code view: legend format for hi coverage */ +span.coverLegendCovHi +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #A7FC9D; +} diff --git a/packages/camera/camera_platform_interface/coverage/html/glass.png b/packages/camera/camera_platform_interface/coverage/html/glass.png new file mode 100644 index 0000000000000000000000000000000000000000..e1abc00680a3093c49fdb775ae6bdb6764c95af2 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html new file mode 100644 index 000000000000..6615ccc56576 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html new file mode 100644 index 000000000000..738b57014f22 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/index.html b/packages/camera/camera_platform_interface/coverage/html/index.html new file mode 100644 index 000000000000..4a459d170aa7 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html new file mode 100644 index 000000000000..1ed65bcc45af --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - platform_interface/camera_platform.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html new file mode 100644 index 000000000000..eadf62bde488 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - platform_interface/camera_platform.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html new file mode 100644 index 000000000000..b667827592e8 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html @@ -0,0 +1,191 @@ + + + + + + + LCOV - lcov.info - platform_interface/camera_platform.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:async';
+       6             : 
+       7             : import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+       8             : import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
+       9             : 
+      10             : import '../../camera_platform_interface.dart';
+      11             : 
+      12             : /// The interface that implementations of camera must implement.
+      13             : ///
+      14             : /// Platform implementations should extend this class rather than implement it as `camera`
+      15             : /// does not consider newly added methods to be breaking changes. Extending this class
+      16             : /// (using `extends`) ensures that the subclass will get the default implementation, while
+      17             : /// platform implementations that `implements` this interface will be broken by newly added
+      18             : /// [CameraPlatform] methods.
+      19             : abstract class CameraPlatform extends PlatformInterface {
+      20             :   /// Constructs a CameraPlatform.
+      21           3 :   CameraPlatform() : super(token: _token);
+      22             : 
+      23           3 :   static final Object _token = Object();
+      24             : 
+      25           3 :   static CameraPlatform _instance = MethodChannelCamera();
+      26             : 
+      27             :   /// The default instance of [CameraPlatform] to use.
+      28             :   ///
+      29             :   /// Defaults to [MethodChannelCamera].
+      30           2 :   static CameraPlatform get instance => _instance;
+      31             : 
+      32             :   /// Platform-specific plugins should set this with their own platform-specific
+      33             :   /// class that extends [CameraPlatform] when they register themselves.
+      34           1 :   static set instance(CameraPlatform instance) {
+      35           2 :     PlatformInterface.verifyToken(instance, _token);
+      36             :     _instance = instance;
+      37             :   }
+      38             : 
+      39             :   /// Completes with a list of available cameras.
+      40           1 :   Future<List<CameraDescription>> availableCameras() {
+      41           1 :     throw UnimplementedError('availableCameras() is not implemented.');
+      42             :   }
+      43             : 
+      44             :   /// Initializes the camera on the device and returns its textureId.
+      45           1 :   Future<int> initializeCamera(
+      46             :     CameraDescription cameraDescription, {
+      47             :     ResolutionPreset resolutionPreset,
+      48             :     bool enableAudio,
+      49             :   }) {
+      50           1 :     throw UnimplementedError('initializeCamera() is not implemented.');
+      51             :   }
+      52             : 
+      53             :   /// Returns a Stream of [CameraEvent]s.
+      54           1 :   Stream<CameraEvent> cameraEventsFor(int textureId) {
+      55           1 :     throw UnimplementedError('videoEventsFor() has not been implemented.');
+      56             :   }
+      57             : 
+      58             :   /// Captures an image and saves it to [path].
+      59           1 :   Future<void> takePicture(int textureId, String path) {
+      60           1 :     throw UnimplementedError('takePicture() is not implemented.');
+      61             :   }
+      62             : 
+      63             :   /// Prepare the capture session for video recording.
+      64           1 :   Future<void> prepareForVideoRecording() {
+      65           1 :     throw UnimplementedError('prepareForVideoRecording() is not implemented.');
+      66             :   }
+      67             : 
+      68             :   /// Start a video recording and save the file to [path].
+      69             :   ///
+      70             :   /// A path can for example be obtained using
+      71             :   /// [path_provider](https://pub.dartlang.org/packages/path_provider).
+      72             :   ///
+      73             :   /// The file is written on the flight as the video is being recorded.
+      74             :   /// If a file already exists at the provided path an error will be thrown.
+      75             :   /// The file can be read as soon as [stopVideoRecording] returns.
+      76           1 :   Future<void> startVideoRecording(int textureId, String path) {
+      77           1 :     throw UnimplementedError('startVideoRecording() is not implemented.');
+      78             :   }
+      79             : 
+      80             :   /// Stop the video recording.
+      81           1 :   Future<void> stopVideoRecording(int textureId) {
+      82           1 :     throw UnimplementedError('stopVideoRecording() is not implemented.');
+      83             :   }
+      84             : 
+      85             :   /// Pause video recording.
+      86           1 :   Future<void> pauseVideoRecording(int textureId) {
+      87           1 :     throw UnimplementedError('pauseVideoRecording() is not implemented.');
+      88             :   }
+      89             : 
+      90             :   /// Resume video recording after pausing.
+      91           1 :   Future<void> resumeVideoRecording(int textureId) {
+      92           1 :     throw UnimplementedError('resumeVideoRecording() is not implemented.');
+      93             :   }
+      94             : 
+      95             :   /// Start streaming images from platform camera.
+      96             :   ///
+      97             :   /// When running continuously with [CameraPreview] widget, this function runs
+      98             :   /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can
+      99             :   /// have significant frame rate drops for [CameraPreview] on lower end
+     100             :   /// devices.
+     101             :   // TODO(bmparr): Add settings for resolution and fps.
+     102           1 :   Future<void> startImageStream(onLatestImageAvailable onAvailable) {
+     103           1 :     throw UnimplementedError('startImageStream() is not implemented.');
+     104             :   }
+     105             : 
+     106             :   /// Stop streaming images from platform camera.
+     107           1 :   Future<void> stopImageStream() {
+     108           1 :     throw UnimplementedError('stopImageStream() is not implemented.');
+     109             :   }
+     110             : 
+     111             :   /// Releases the resources of this camera.
+     112           1 :   Future<void> dispose(int textureId) {
+     113           1 :     throw UnimplementedError('dispose() is not implemented.');
+     114             :   }
+     115             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html new file mode 100644 index 000000000000..58bbd9987ed2 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html @@ -0,0 +1,93 @@ + + + + + + + LCOV - lcov.info - platform_interface + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html new file mode 100644 index 000000000000..d4f9ff9f5ca9 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html @@ -0,0 +1,93 @@ + + + + + + + LCOV - lcov.info - platform_interface + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html new file mode 100644 index 000000000000..413082c13942 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html @@ -0,0 +1,93 @@ + + + + + + + LCOV - lcov.info - platform_interface + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/ruby.png b/packages/camera/camera_platform_interface/coverage/html/ruby.png new file mode 100644 index 0000000000000000000000000000000000000000..991b6d4ec9e78be165e3ef757eed1aada287364d GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/snow.png b/packages/camera/camera_platform_interface/coverage/html/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..2cdae107fceec6e7f02ac7acb4a34a82a540caa5 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html new file mode 100644 index 000000000000..f9ec1768180f --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_description.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html new file mode 100644 index 000000000000..4031fb3e2412 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_description.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html new file mode 100644 index 000000000000..10699d953f8e --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html @@ -0,0 +1,131 @@ + + + + + + + LCOV - lcov.info - types/camera_description.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:ui';
+       6             : 
+       7             : /// The direction the camera is facing.
+       8           2 : enum CameraLensDirection {
+       9             :   /// Front facing camera (a user looking at the screen is seen by the camera).
+      10           2 :   front,
+      11             : 
+      12             :   /// Back facing camera (a user looking at the screen is not seen by the camera).
+      13           2 :   back,
+      14             : 
+      15             :   /// External camera which may not be mounted to the device.
+      16           2 :   external,
+      17             : }
+      18             : 
+      19             : /// Properties of a camera device.
+      20             : class CameraDescription {
+      21             :   /// Creates a new camera description with the given properties.
+      22           0 :   CameraDescription({this.name, this.lensDirection, this.sensorOrientation});
+      23             : 
+      24             :   /// The name of the camera device.
+      25             :   final String name;
+      26             : 
+      27             :   /// The direction the camera is facing.
+      28             :   final CameraLensDirection lensDirection;
+      29             : 
+      30             :   /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.
+      31             :   ///
+      32             :   /// **Range of valid values:**
+      33             :   /// 0, 90, 180, 270
+      34             :   ///
+      35             :   /// On Android, also defines the direction of rolling shutter readout, which
+      36             :   /// is from top to bottom in the sensor's coordinate system.
+      37             :   final int sensorOrientation;
+      38             : 
+      39           0 :   @override
+      40             :   bool operator ==(Object o) {
+      41           0 :     return o is CameraDescription &&
+      42           0 :         o.name == name &&
+      43           0 :         o.lensDirection == lensDirection;
+      44             :   }
+      45             : 
+      46           0 :   @override
+      47             :   int get hashCode {
+      48           0 :     return hashValues(name, lensDirection);
+      49             :   }
+      50             : 
+      51           0 :   @override
+      52             :   String toString() {
+      53           0 :     return '$runtimeType($name, $lensDirection, $sensorOrientation)';
+      54             :   }
+      55             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html new file mode 100644 index 000000000000..f3a43cd1a63b --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html new file mode 100644 index 000000000000..f364a44cca63 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html new file mode 100644 index 000000000000..6b9684af05ce --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html @@ -0,0 +1,152 @@ + + + + + + + LCOV - lcov.info - types/camera_event.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2017 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:ui';
+       6             : 
+       7             : import 'package:meta/meta.dart';
+       8             : 
+       9             : /// Event emitted from the platform implementation.
+      10             : class CameraEvent {
+      11             :   /// Creates an instance of [CameraEvent].
+      12             :   ///
+      13             :   /// The [eventType] argument is required.
+      14             :   ///
+      15             :   /// Depending on the [eventType], the [captureSize], [previewSize] and 
+      16             :   /// [errorDescription] arguments can be null.
+      17           0 :   CameraEvent({
+      18             :     @required this.eventType,
+      19             :     this.captureSize,
+      20             :     this.previewSize,
+      21             :     this.errorDescription,
+      22             :   });
+      23             : 
+      24             :   /// The type of the event.
+      25             :   final CameraEventType eventType;
+      26             : 
+      27             :   /// The capture size in pixels.
+      28             :   /// 
+      29             :   /// Only used if the [eventType] is [CameraEventType.initialized]
+      30             :   final Size captureSize;
+      31             : 
+      32             :   /// The size of the preview in pixels.
+      33             :   /// 
+      34             :   /// Only used if the [eventType] is [CameraEventType.initialized]
+      35             :   final Size previewSize;
+      36             : 
+      37             :   /// Description of the error.
+      38             :   ///
+      39             :   /// Only used if [eventType] is [CameraEventType.error].
+      40             :   final String errorDescription;
+      41             : 
+      42           0 :   @override
+      43             :   bool operator ==(Object other) {
+      44             :     return identical(this, other) ||
+      45           0 :         other is CameraEvent &&
+      46           0 :             runtimeType == other.runtimeType &&
+      47           0 :             eventType == other.eventType &&
+      48           0 :             captureSize == other.captureSize &&
+      49           0 :             previewSize == other.previewSize &&
+      50           0 :             errorDescription == other.errorDescription;
+      51             :   }
+      52             : 
+      53           0 :   @override
+      54             :   int get hashCode =>
+      55           0 :       eventType.hashCode ^
+      56           0 :       captureSize.hashCode ^
+      57           0 :       previewSize.hashCode ^
+      58           0 :       errorDescription.hashCode;
+      59             : }
+      60             : 
+      61             : /// Type of the event.
+      62             : ///
+      63             : /// Emitted by the platform implementation when the camera is closing or
+      64             : /// if an error occured.
+      65           2 : enum CameraEventType {
+      66             :   /// The camera has been initialized.
+      67             : 
+      68             :   /// The camera is closing.
+      69           2 :   cameraClosing,
+      70             : 
+      71             :   /// An error occured while accessing the camera.
+      72           2 :   error,
+      73             : 
+      74             :   /// An unknown event has been received.
+      75           2 :   unknown,
+      76             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html new file mode 100644 index 000000000000..5cf21835a9fc --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_image.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html new file mode 100644 index 000000000000..945e78a95d84 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/camera_image.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html new file mode 100644 index 000000000000..96ab1f424ec4 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html @@ -0,0 +1,224 @@ + + + + + + + LCOV - lcov.info - types/camera_image.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:typed_data';
+       6             : 
+       7             : import 'package:flutter/foundation.dart';
+       8             : 
+       9             : /// A single color plane of image data.
+      10             : ///
+      11             : /// The number and meaning of the planes in an image are determined by the
+      12             : /// format of the Image.
+      13             : class Plane {
+      14           1 :   Plane._fromPlatformData(Map<dynamic, dynamic> data)
+      15           1 :       : bytes = data['bytes'],
+      16           1 :         bytesPerPixel = data['bytesPerPixel'],
+      17           1 :         bytesPerRow = data['bytesPerRow'],
+      18           1 :         height = data['height'],
+      19           1 :         width = data['width'];
+      20             : 
+      21             :   /// Bytes representing this plane.
+      22             :   final Uint8List bytes;
+      23             : 
+      24             :   /// The distance between adjacent pixel samples on Android, in bytes.
+      25             :   ///
+      26             :   /// Will be `null` on iOS.
+      27             :   final int bytesPerPixel;
+      28             : 
+      29             :   /// The row stride for this color plane, in bytes.
+      30             :   final int bytesPerRow;
+      31             : 
+      32             :   /// Height of the pixel buffer on iOS.
+      33             :   ///
+      34             :   /// Will be `null` on Android
+      35             :   final int height;
+      36             : 
+      37             :   /// Width of the pixel buffer on iOS.
+      38             :   ///
+      39             :   /// Will be `null` on Android.
+      40             :   final int width;
+      41             : }
+      42             : 
+      43             : // TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values.
+      44             : /// Group of image formats that are comparable across Android and iOS platforms.
+      45           2 : enum ImageFormatGroup {
+      46             :   /// The image format does not fit into any specific group.
+      47           2 :   unknown,
+      48             : 
+      49             :   /// Multi-plane YUV 420 format.
+      50             :   ///
+      51             :   /// This format is a generic YCbCr format, capable of describing any 4:2:0
+      52             :   /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
+      53             :   /// with 8 bits per color sample.
+      54             :   ///
+      55             :   /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See
+      56             :   /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
+      57             :   ///
+      58             :   /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See
+      59             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc
+      60           2 :   yuv420,
+      61             : 
+      62             :   /// 32-bit BGRA.
+      63             :   ///
+      64             :   /// On iOS, this is `kCVPixelFormatType_32BGRA`. See
+      65             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc
+      66           2 :   bgra8888,
+      67             : }
+      68             : 
+      69             : /// Describes how pixels are represented in an image.
+      70             : class ImageFormat {
+      71           2 :   ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
+      72             : 
+      73             :   /// Describes the format group the raw image format falls into.
+      74             :   final ImageFormatGroup group;
+      75             : 
+      76             :   /// Raw version of the format from the Android or iOS platform.
+      77             :   ///
+      78             :   /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
+      79             :   /// https://developer.android.com/reference/android/graphics/ImageFormat
+      80             :   ///
+      81             :   /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
+      82             :   /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
+      83             :   final dynamic raw;
+      84             : }
+      85             : 
+      86           1 : ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
+      87           2 :   if (defaultTargetPlatform == TargetPlatform.android) {
+      88             :     // android.graphics.ImageFormat.YUV_420_888
+      89           1 :     if (rawFormat == 35) {
+      90             :       return ImageFormatGroup.yuv420;
+      91             :     }
+      92             :   }
+      93             : 
+      94           2 :   if (defaultTargetPlatform == TargetPlatform.iOS) {
+      95             :     switch (rawFormat) {
+      96             :       // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
+      97           1 :       case 875704438:
+      98             :         return ImageFormatGroup.yuv420;
+      99             :       // kCVPixelFormatType_32BGRA
+     100           1 :       case 1111970369:
+     101             :         return ImageFormatGroup.bgra8888;
+     102             :     }
+     103             :   }
+     104             : 
+     105             :   return ImageFormatGroup.unknown;
+     106             : }
+     107             : 
+     108             : /// A single complete image buffer from the platform camera.
+     109             : ///
+     110             : /// This class allows for direct application access to the pixel data of an
+     111             : /// Image through one or more [Uint8List]. Each buffer is encapsulated in a
+     112             : /// [Plane] that describes the layout of the pixel data in that plane. The
+     113             : /// [CameraImage] is not directly usable as a UI resource.
+     114             : ///
+     115             : /// Although not all image formats are planar on iOS, we treat 1-dimensional
+     116             : /// images as single planar images.
+     117             : class CameraImage {
+     118             :   /// Creates a new [CameraImage] from the data received from the platform.
+     119           1 :   CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
+     120           2 :       : format = ImageFormat._fromPlatformData(data['format']),
+     121           1 :         height = data['height'],
+     122           1 :         width = data['width'],
+     123           2 :         planes = List<Plane>.unmodifiable(data['planes']
+     124           3 :             .map((dynamic planeData) => Plane._fromPlatformData(planeData)));
+     125             : 
+     126             :   /// Format of the image provided.
+     127             :   ///
+     128             :   /// Determines the number of planes needed to represent the image, and
+     129             :   /// the general layout of the pixel data in each [Uint8List].
+     130             :   final ImageFormat format;
+     131             : 
+     132             :   /// Height of the image in pixels.
+     133             :   ///
+     134             :   /// For formats where some color channels are subsampled, this is the height
+     135             :   /// of the largest-resolution plane.
+     136             :   final int height;
+     137             : 
+     138             :   /// Width of the image in pixels.
+     139             :   ///
+     140             :   /// For formats where some color channels are subsampled, this is the width
+     141             :   /// of the largest-resolution plane.
+     142             :   final int width;
+     143             : 
+     144             :   /// The pixels planes for this image.
+     145             :   ///
+     146             :   /// The number of planes is determined by the format of the image.
+     147             :   final List<Plane> planes;
+     148             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html new file mode 100644 index 000000000000..5cf500911692 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html @@ -0,0 +1,123 @@ + + + + + + + LCOV - lcov.info - types + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html new file mode 100644 index 000000000000..913571e880cb --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html @@ -0,0 +1,123 @@ + + + + + + + LCOV - lcov.info - types + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index.html b/packages/camera/camera_platform_interface/coverage/html/types/index.html new file mode 100644 index 000000000000..479a3edaa376 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index.html @@ -0,0 +1,123 @@ + + + + + + + LCOV - lcov.info - types + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html new file mode 100644 index 000000000000..fdf79712000a --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/resolution_preset.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html new file mode 100644 index 000000000000..40ed9b1de628 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + LCOV - lcov.info - types/resolution_preset.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html new file mode 100644 index 000000000000..05c40ae95295 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html @@ -0,0 +1,98 @@ + + + + + + + LCOV - lcov.info - types/resolution_preset.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : /// Affect the quality of video recording and image capture:
+       2             : ///
+       3             : /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically.
+       4           2 : enum ResolutionPreset {
+       5             :   /// 352x288 on iOS, 240p (320x240) on Android
+       6           2 :   low,
+       7             : 
+       8             :   /// 480p (640x480 on iOS, 720x480 on Android)
+       9           2 :   medium,
+      10             : 
+      11             :   /// 720p (1280x720)
+      12           2 :   high,
+      13             : 
+      14             :   /// 1080p (1920x1080)
+      15           2 :   veryHigh,
+      16             : 
+      17             :   /// 2160p (3840x2160)
+      18           2 :   ultraHigh,
+      19             : 
+      20             :   /// The highest resolution available.
+      21           2 :   max,
+      22             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/updown.png b/packages/camera/camera_platform_interface/coverage/html/updown.png new file mode 100644 index 0000000000000000000000000000000000000000..aa56a238b3e6c435265250f9266cd1b8caba0f20 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/lcov.info b/packages/camera/camera_platform_interface/coverage/lcov.info new file mode 100644 index 000000000000..f8be39954cf2 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/lcov.info @@ -0,0 +1,110 @@ +SF:lib/src/types/camera_image.dart +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:18,1 +DA:19,1 +DA:45,2 +DA:47,2 +DA:60,2 +DA:66,2 +DA:71,2 +DA:86,1 +DA:87,2 +DA:89,1 +DA:94,2 +DA:97,1 +DA:100,1 +DA:119,1 +DA:120,2 +DA:121,1 +DA:122,1 +DA:123,2 +DA:124,3 +LF:23 +LH:23 +end_of_record +SF:lib/src/platform_interface/camera_platform.dart +DA:21,3 +DA:23,3 +DA:25,3 +DA:30,2 +DA:34,1 +DA:35,2 +DA:40,1 +DA:41,1 +DA:45,1 +DA:50,1 +DA:54,1 +DA:55,1 +DA:59,1 +DA:60,1 +DA:64,1 +DA:65,1 +DA:76,1 +DA:77,1 +DA:81,1 +DA:82,1 +DA:86,1 +DA:87,1 +DA:91,1 +DA:92,1 +DA:102,1 +DA:103,1 +DA:107,1 +DA:108,1 +DA:112,1 +DA:113,1 +LF:30 +LH:30 +end_of_record +SF:lib/src/types/camera_description.dart +DA:8,2 +DA:10,2 +DA:13,2 +DA:16,2 +DA:22,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:46,0 +DA:48,0 +DA:51,0 +DA:53,0 +LF:13 +LH:4 +end_of_record +SF:lib/src/types/camera_event.dart +DA:17,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:65,2 +DA:69,2 +DA:72,2 +DA:75,2 +LF:17 +LH:4 +end_of_record +SF:lib/src/types/resolution_preset.dart +DA:4,2 +DA:6,2 +DA:9,2 +DA:12,2 +DA:15,2 +DA:18,2 +DA:21,2 +LF:7 +LH:7 +end_of_record diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart new file mode 100644 index 000000000000..edef7929a761 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart @@ -0,0 +1,11 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'types.dart'; + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraPlatform.startImageStream]. +// ignore: inference_failure_on_function_return_type +typedef onLatestImageAvailable = Function(CameraImage image); diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart new file mode 100644 index 000000000000..7b9b47381e28 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart @@ -0,0 +1,76 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:meta/meta.dart'; + +/// Event emitted from the platform implementation. +class CameraEvent { + /// Creates an instance of [CameraEvent]. + /// + /// The [eventType] argument is required. + /// + /// Depending on the [eventType], the [captureSize], [previewSize] and + /// [errorDescription] arguments can be null. + CameraEvent({ + @required this.eventType, + this.captureSize, + this.previewSize, + this.errorDescription, + }); + + /// The type of the event. + final CameraEventType eventType; + + /// The capture size in pixels. + /// + /// Only used if the [eventType] is [CameraEventType.initialized] + final Size captureSize; + + /// The size of the preview in pixels. + /// + /// Only used if the [eventType] is [CameraEventType.initialized] + final Size previewSize; + + /// Description of the error. + /// + /// Only used if [eventType] is [CameraEventType.error]. + final String errorDescription; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is CameraEvent && + runtimeType == other.runtimeType && + eventType == other.eventType && + captureSize == other.captureSize && + previewSize == other.previewSize && + errorDescription == other.errorDescription; + } + + @override + int get hashCode => + eventType.hashCode ^ + captureSize.hashCode ^ + previewSize.hashCode ^ + errorDescription.hashCode; +} + +/// Type of the event. +/// +/// Emitted by the platform implementation when the camera is closing or +/// if an error occured. +enum CameraEventType { + /// The camera has been initialized. + + /// The camera is closing. + cameraClosing, + + /// An error occured while accessing the camera. + error, + + /// An unknown event has been received. + unknown, +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart new file mode 100644 index 000000000000..5662f130d8a9 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart @@ -0,0 +1,148 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; + +/// A single color plane of image data. +/// +/// The number and meaning of the planes in an image are determined by the +/// format of the Image. +class Plane { + Plane._fromPlatformData(Map data) + : bytes = data['bytes'], + bytesPerPixel = data['bytesPerPixel'], + bytesPerRow = data['bytesPerRow'], + height = data['height'], + width = data['width']; + + /// Bytes representing this plane. + final Uint8List bytes; + + /// The distance between adjacent pixel samples on Android, in bytes. + /// + /// Will be `null` on iOS. + final int bytesPerPixel; + + /// The row stride for this color plane, in bytes. + final int bytesPerRow; + + /// Height of the pixel buffer on iOS. + /// + /// Will be `null` on Android + final int height; + + /// Width of the pixel buffer on iOS. + /// + /// Will be `null` on Android. + final int width; +} + +// TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values. +/// Group of image formats that are comparable across Android and iOS platforms. +enum ImageFormatGroup { + /// The image format does not fit into any specific group. + unknown, + + /// Multi-plane YUV 420 format. + /// + /// This format is a generic YCbCr format, capable of describing any 4:2:0 + /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved), + /// with 8 bits per color sample. + /// + /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See + /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 + /// + /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See + /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc + yuv420, + + /// 32-bit BGRA. + /// + /// On iOS, this is `kCVPixelFormatType_32BGRA`. See + /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc + bgra8888, +} + +/// Describes how pixels are represented in an image. +class ImageFormat { + ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); + + /// Describes the format group the raw image format falls into. + final ImageFormatGroup group; + + /// Raw version of the format from the Android or iOS platform. + /// + /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See + /// https://developer.android.com/reference/android/graphics/ImageFormat + /// + /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. + /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc + final dynamic raw; +} + +ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { + if (defaultTargetPlatform == TargetPlatform.android) { + // android.graphics.ImageFormat.YUV_420_888 + if (rawFormat == 35) { + return ImageFormatGroup.yuv420; + } + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + switch (rawFormat) { + // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + case 875704438: + return ImageFormatGroup.yuv420; + // kCVPixelFormatType_32BGRA + case 1111970369: + return ImageFormatGroup.bgra8888; + } + } + + return ImageFormatGroup.unknown; +} + +/// A single complete image buffer from the platform camera. +/// +/// This class allows for direct application access to the pixel data of an +/// Image through one or more [Uint8List]. Each buffer is encapsulated in a +/// [Plane] that describes the layout of the pixel data in that plane. The +/// [CameraImage] is not directly usable as a UI resource. +/// +/// Although not all image formats are planar on iOS, we treat 1-dimensional +/// images as single planar images. +class CameraImage { + /// Creates a new [CameraImage] from the data received from the platform. + CameraImage.fromPlatformData(Map data) + : format = ImageFormat._fromPlatformData(data['format']), + height = data['height'], + width = data['width'], + planes = List.unmodifiable(data['planes'] + .map((dynamic planeData) => Plane._fromPlatformData(planeData))); + + /// Format of the image provided. + /// + /// Determines the number of planes needed to represent the image, and + /// the general layout of the pixel data in each [Uint8List]. + final ImageFormat format; + + /// Height of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the height + /// of the largest-resolution plane. + final int height; + + /// Width of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the width + /// of the largest-resolution plane. + final int width; + + /// The pixels planes for this image. + /// + /// The number of planes is determined by the format of the image. + final List planes; +} diff --git a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart b/packages/camera/camera_platform_interface/test/types/camera_image_test.dart new file mode 100644 index 000000000000..61ed75225cdf --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/camera_image_test.dart @@ -0,0 +1,113 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:camera_platform_interface/src/types/camera_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('$CameraImage tests', () { + test('$CameraImage can be created', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.height, 1); + expect(cameraImage.width, 4); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.planes.length, 1); + }); + + test('$CameraImage has ImageFormatGroup.yuv420 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 875704438, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('$CameraImage has ImageFormatGroup.yuv420 for Android', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('$CameraImage has ImageFormatGroup.bgra8888 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 1111970369, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.bgra8888); + }); + test('$CameraImage has ImageFormatGroup.unknown', () { + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': null, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.unknown); + }); + }); +} From d0be649d663b9c8a46622e24a893a1286b94e6fe Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 6 Nov 2020 17:15:10 +0100 Subject: [PATCH 02/67] Remove test coverage folder --- .../coverage/html/amber.png | Bin 141 -> 0 bytes .../coverage/html/emerald.png | Bin 141 -> 0 bytes .../coverage/html/gcov.css | 519 ------------------ .../coverage/html/glass.png | Bin 167 -> 0 bytes .../coverage/html/index-sort-f.html | 103 ---- .../coverage/html/index-sort-l.html | 103 ---- .../coverage/html/index.html | 103 ---- .../camera_platform.dart.func-sort-c.html | 72 --- .../camera_platform.dart.func.html | 72 --- .../camera_platform.dart.gcov.html | 191 ------- .../html/platform_interface/index-sort-f.html | 93 ---- .../html/platform_interface/index-sort-l.html | 93 ---- .../html/platform_interface/index.html | 93 ---- .../coverage/html/ruby.png | Bin 141 -> 0 bytes .../coverage/html/snow.png | Bin 141 -> 0 bytes .../camera_description.dart.func-sort-c.html | 72 --- .../types/camera_description.dart.func.html | 72 --- .../types/camera_description.dart.gcov.html | 131 ----- .../types/camera_event.dart.func-sort-c.html | 72 --- .../html/types/camera_event.dart.func.html | 72 --- .../html/types/camera_event.dart.gcov.html | 152 ----- .../types/camera_image.dart.func-sort-c.html | 72 --- .../html/types/camera_image.dart.func.html | 72 --- .../html/types/camera_image.dart.gcov.html | 224 -------- .../coverage/html/types/index-sort-f.html | 123 ----- .../coverage/html/types/index-sort-l.html | 123 ----- .../coverage/html/types/index.html | 123 ----- .../resolution_preset.dart.func-sort-c.html | 72 --- .../types/resolution_preset.dart.func.html | 72 --- .../types/resolution_preset.dart.gcov.html | 98 ---- .../coverage/html/updown.png | Bin 117 -> 0 bytes .../coverage/lcov.info | 110 ---- 32 files changed, 3102 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/coverage/html/amber.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/emerald.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/gcov.css delete mode 100644 packages/camera/camera_platform_interface/coverage/html/glass.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/ruby.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/snow.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/updown.png delete mode 100644 packages/camera/camera_platform_interface/coverage/lcov.info diff --git a/packages/camera/camera_platform_interface/coverage/html/amber.png b/packages/camera/camera_platform_interface/coverage/html/amber.png deleted file mode 100644 index 2cab170d8359081983a4e343848dfe06bc490f12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 diff --git a/packages/camera/camera_platform_interface/coverage/html/gcov.css b/packages/camera/camera_platform_interface/coverage/html/gcov.css deleted file mode 100644 index bfd0a83e10b5..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/gcov.css +++ /dev/null @@ -1,519 +0,0 @@ -/* All views: initial background and text color */ -body -{ - color: #000000; - background-color: #FFFFFF; -} - -/* All views: standard link format*/ -a:link -{ - color: #284FA8; - text-decoration: underline; -} - -/* All views: standard link - visited format */ -a:visited -{ - color: #00CB40; - text-decoration: underline; -} - -/* All views: standard link - activated format */ -a:active -{ - color: #FF0040; - text-decoration: underline; -} - -/* All views: main title format */ -td.title -{ - text-align: center; - padding-bottom: 10px; - font-family: sans-serif; - font-size: 20pt; - font-style: italic; - font-weight: bold; -} - -/* All views: header item format */ -td.headerItem -{ - text-align: right; - padding-right: 6px; - font-family: sans-serif; - font-weight: bold; - vertical-align: top; - white-space: nowrap; -} - -/* All views: header item value format */ -td.headerValue -{ - text-align: left; - color: #284FA8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; -} - -/* All views: header item coverage table heading */ -td.headerCovTableHead -{ - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - font-size: 80%; - white-space: nowrap; -} - -/* All views: header item coverage table entry */ -td.headerCovTableEntry -{ - text-align: right; - color: #284FA8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #DAE7FE; -} - -/* All views: header item coverage table entry for high coverage rate */ -td.headerCovTableEntryHi -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #A7FC9D; -} - -/* All views: header item coverage table entry for medium coverage rate */ -td.headerCovTableEntryMed -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #FFEA20; -} - -/* All views: header item coverage table entry for ow coverage rate */ -td.headerCovTableEntryLo -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #FF0000; -} - -/* All views: header legend value for legend entry */ -td.headerValueLeg -{ - text-align: left; - color: #000000; - font-family: sans-serif; - font-size: 80%; - white-space: nowrap; - padding-top: 4px; -} - -/* All views: color of horizontal ruler */ -td.ruler -{ - background-color: #6688D4; -} - -/* All views: version string format */ -td.versionInfo -{ - text-align: center; - padding-top: 2px; - font-family: sans-serif; - font-style: italic; -} - -/* Directory view/File view (all)/Test case descriptions: - table headline format */ -td.tableHead -{ - text-align: center; - color: #FFFFFF; - background-color: #6688D4; - font-family: sans-serif; - font-size: 120%; - font-weight: bold; - white-space: nowrap; - padding-left: 4px; - padding-right: 4px; -} - -span.tableHeadSort -{ - padding-right: 4px; -} - -/* Directory view/File view (all): filename entry format */ -td.coverFile -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284FA8; - background-color: #DAE7FE; - font-family: monospace; -} - -/* Directory view/File view (all): bar-graph entry format*/ -td.coverBar -{ - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; -} - -/* Directory view/File view (all): bar-graph outline color */ -td.coverBarOutline -{ - background-color: #000000; -} - -/* Directory view/File view (all): percentage entry for files with - high coverage rate */ -td.coverPerHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #A7FC9D; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - high coverage rate */ -td.coverNumHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #A7FC9D; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - medium coverage rate */ -td.coverPerMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FFEA20; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - medium coverage rate */ -td.coverNumMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FFEA20; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - low coverage rate */ -td.coverPerLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - low coverage rate */ -td.coverNumLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - white-space: nowrap; - font-family: sans-serif; -} - -/* File view (all): "show/hide details" link format */ -a.detail:link -{ - color: #B8D0FF; - font-size:80%; -} - -/* File view (all): "show/hide details" link - visited format */ -a.detail:visited -{ - color: #B8D0FF; - font-size:80%; -} - -/* File view (all): "show/hide details" link - activated format */ -a.detail:active -{ - color: #FFFFFF; - font-size:80%; -} - -/* File view (detail): test name entry */ -td.testName -{ - text-align: right; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* File view (detail): test percentage entry */ -td.testPer -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* File view (detail): test lines count entry */ -td.testNum -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* Test case descriptions: test name format*/ -dt -{ - font-family: sans-serif; - font-weight: bold; -} - -/* Test case descriptions: description table body */ -td.testDescription -{ - padding-top: 10px; - padding-left: 30px; - padding-bottom: 10px; - padding-right: 30px; - background-color: #DAE7FE; -} - -/* Source code view: function entry */ -td.coverFn -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284FA8; - background-color: #DAE7FE; - font-family: monospace; -} - -/* Source code view: function entry zero count*/ -td.coverFnLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: function entry nonzero count*/ -td.coverFnHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: source code format */ -pre.source -{ - font-family: monospace; - white-space: pre; - margin-top: 2px; -} - -/* Source code view: line number format */ -span.lineNum -{ - background-color: #EFE383; -} - -/* Source code view: format for lines which were executed */ -td.lineCov, -span.lineCov -{ - background-color: #CAD7FE; -} - -/* Source code view: format for Cov legend */ -span.coverLegendCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #CAD7FE; -} - -/* Source code view: format for lines which were not executed */ -td.lineNoCov, -span.lineNoCov -{ - background-color: #FF6230; -} - -/* Source code view: format for NoCov legend */ -span.coverLegendNoCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #FF6230; -} - -/* Source code view (function table): standard link - visited format */ -td.lineNoCov > a:visited, -td.lineCov > a:visited -{ - color: black; - text-decoration: underline; -} - -/* Source code view: format for lines which were executed only in a - previous version */ -span.lineDiffCov -{ - background-color: #B5F7AF; -} - -/* Source code view: format for branches which were executed - * and taken */ -span.branchCov -{ - background-color: #CAD7FE; -} - -/* Source code view: format for branches which were executed - * but not taken */ -span.branchNoCov -{ - background-color: #FF6230; -} - -/* Source code view: format for branches which were not executed */ -span.branchNoExec -{ - background-color: #FF6230; -} - -/* Source code view: format for the source code heading line */ -pre.sourceHeading -{ - white-space: pre; - font-family: monospace; - font-weight: bold; - margin: 0px; -} - -/* All views: header legend value for low rate */ -td.headerValueLegL -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 4px; - padding-right: 2px; - background-color: #FF0000; - font-size: 80%; -} - -/* All views: header legend value for med rate */ -td.headerValueLegM -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 2px; - background-color: #FFEA20; - font-size: 80%; -} - -/* All views: header legend value for hi rate */ -td.headerValueLegH -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 4px; - background-color: #A7FC9D; - font-size: 80%; -} - -/* All views except source code view: legend format for low coverage */ -span.coverLegendCovLo -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #FF0000; -} - -/* All views except source code view: legend format for med coverage */ -span.coverLegendCovMed -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #FFEA20; -} - -/* All views except source code view: legend format for hi coverage */ -span.coverLegendCovHi -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #A7FC9D; -} diff --git a/packages/camera/camera_platform_interface/coverage/html/glass.png b/packages/camera/camera_platform_interface/coverage/html/glass.png deleted file mode 100644 index e1abc00680a3093c49fdb775ae6bdb6764c95af2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html deleted file mode 100644 index 6615ccc56576..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - LCOV - lcov.info - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html deleted file mode 100644 index 738b57014f22..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - LCOV - lcov.info - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/index.html b/packages/camera/camera_platform_interface/coverage/html/index.html deleted file mode 100644 index 4a459d170aa7..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - LCOV - lcov.info - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html deleted file mode 100644 index 1ed65bcc45af..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface/camera_platform.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html deleted file mode 100644 index eadf62bde488..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface/camera_platform.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html deleted file mode 100644 index b667827592e8..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface/camera_platform.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:async';
-       6             : 
-       7             : import 'package:plugin_platform_interface/plugin_platform_interface.dart';
-       8             : import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
-       9             : 
-      10             : import '../../camera_platform_interface.dart';
-      11             : 
-      12             : /// The interface that implementations of camera must implement.
-      13             : ///
-      14             : /// Platform implementations should extend this class rather than implement it as `camera`
-      15             : /// does not consider newly added methods to be breaking changes. Extending this class
-      16             : /// (using `extends`) ensures that the subclass will get the default implementation, while
-      17             : /// platform implementations that `implements` this interface will be broken by newly added
-      18             : /// [CameraPlatform] methods.
-      19             : abstract class CameraPlatform extends PlatformInterface {
-      20             :   /// Constructs a CameraPlatform.
-      21           3 :   CameraPlatform() : super(token: _token);
-      22             : 
-      23           3 :   static final Object _token = Object();
-      24             : 
-      25           3 :   static CameraPlatform _instance = MethodChannelCamera();
-      26             : 
-      27             :   /// The default instance of [CameraPlatform] to use.
-      28             :   ///
-      29             :   /// Defaults to [MethodChannelCamera].
-      30           2 :   static CameraPlatform get instance => _instance;
-      31             : 
-      32             :   /// Platform-specific plugins should set this with their own platform-specific
-      33             :   /// class that extends [CameraPlatform] when they register themselves.
-      34           1 :   static set instance(CameraPlatform instance) {
-      35           2 :     PlatformInterface.verifyToken(instance, _token);
-      36             :     _instance = instance;
-      37             :   }
-      38             : 
-      39             :   /// Completes with a list of available cameras.
-      40           1 :   Future<List<CameraDescription>> availableCameras() {
-      41           1 :     throw UnimplementedError('availableCameras() is not implemented.');
-      42             :   }
-      43             : 
-      44             :   /// Initializes the camera on the device and returns its textureId.
-      45           1 :   Future<int> initializeCamera(
-      46             :     CameraDescription cameraDescription, {
-      47             :     ResolutionPreset resolutionPreset,
-      48             :     bool enableAudio,
-      49             :   }) {
-      50           1 :     throw UnimplementedError('initializeCamera() is not implemented.');
-      51             :   }
-      52             : 
-      53             :   /// Returns a Stream of [CameraEvent]s.
-      54           1 :   Stream<CameraEvent> cameraEventsFor(int textureId) {
-      55           1 :     throw UnimplementedError('videoEventsFor() has not been implemented.');
-      56             :   }
-      57             : 
-      58             :   /// Captures an image and saves it to [path].
-      59           1 :   Future<void> takePicture(int textureId, String path) {
-      60           1 :     throw UnimplementedError('takePicture() is not implemented.');
-      61             :   }
-      62             : 
-      63             :   /// Prepare the capture session for video recording.
-      64           1 :   Future<void> prepareForVideoRecording() {
-      65           1 :     throw UnimplementedError('prepareForVideoRecording() is not implemented.');
-      66             :   }
-      67             : 
-      68             :   /// Start a video recording and save the file to [path].
-      69             :   ///
-      70             :   /// A path can for example be obtained using
-      71             :   /// [path_provider](https://pub.dartlang.org/packages/path_provider).
-      72             :   ///
-      73             :   /// The file is written on the flight as the video is being recorded.
-      74             :   /// If a file already exists at the provided path an error will be thrown.
-      75             :   /// The file can be read as soon as [stopVideoRecording] returns.
-      76           1 :   Future<void> startVideoRecording(int textureId, String path) {
-      77           1 :     throw UnimplementedError('startVideoRecording() is not implemented.');
-      78             :   }
-      79             : 
-      80             :   /// Stop the video recording.
-      81           1 :   Future<void> stopVideoRecording(int textureId) {
-      82           1 :     throw UnimplementedError('stopVideoRecording() is not implemented.');
-      83             :   }
-      84             : 
-      85             :   /// Pause video recording.
-      86           1 :   Future<void> pauseVideoRecording(int textureId) {
-      87           1 :     throw UnimplementedError('pauseVideoRecording() is not implemented.');
-      88             :   }
-      89             : 
-      90             :   /// Resume video recording after pausing.
-      91           1 :   Future<void> resumeVideoRecording(int textureId) {
-      92           1 :     throw UnimplementedError('resumeVideoRecording() is not implemented.');
-      93             :   }
-      94             : 
-      95             :   /// Start streaming images from platform camera.
-      96             :   ///
-      97             :   /// When running continuously with [CameraPreview] widget, this function runs
-      98             :   /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can
-      99             :   /// have significant frame rate drops for [CameraPreview] on lower end
-     100             :   /// devices.
-     101             :   // TODO(bmparr): Add settings for resolution and fps.
-     102           1 :   Future<void> startImageStream(onLatestImageAvailable onAvailable) {
-     103           1 :     throw UnimplementedError('startImageStream() is not implemented.');
-     104             :   }
-     105             : 
-     106             :   /// Stop streaming images from platform camera.
-     107           1 :   Future<void> stopImageStream() {
-     108           1 :     throw UnimplementedError('stopImageStream() is not implemented.');
-     109             :   }
-     110             : 
-     111             :   /// Releases the resources of this camera.
-     112           1 :   Future<void> dispose(int textureId) {
-     113           1 :     throw UnimplementedError('dispose() is not implemented.');
-     114             :   }
-     115             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html deleted file mode 100644 index 58bbd9987ed2..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html deleted file mode 100644 index d4f9ff9f5ca9..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html deleted file mode 100644 index 413082c13942..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - LCOV - lcov.info - platform_interface - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/ruby.png b/packages/camera/camera_platform_interface/coverage/html/ruby.png deleted file mode 100644 index 991b6d4ec9e78be165e3ef757eed1aada287364d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS diff --git a/packages/camera/camera_platform_interface/coverage/html/snow.png b/packages/camera/camera_platform_interface/coverage/html/snow.png deleted file mode 100644 index 2cdae107fceec6e7f02ac7acb4a34a82a540caa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html deleted file mode 100644 index f9ec1768180f..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_description.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html deleted file mode 100644 index 4031fb3e2412..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_description.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html deleted file mode 100644 index 10699d953f8e..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_description.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:ui';
-       6             : 
-       7             : /// The direction the camera is facing.
-       8           2 : enum CameraLensDirection {
-       9             :   /// Front facing camera (a user looking at the screen is seen by the camera).
-      10           2 :   front,
-      11             : 
-      12             :   /// Back facing camera (a user looking at the screen is not seen by the camera).
-      13           2 :   back,
-      14             : 
-      15             :   /// External camera which may not be mounted to the device.
-      16           2 :   external,
-      17             : }
-      18             : 
-      19             : /// Properties of a camera device.
-      20             : class CameraDescription {
-      21             :   /// Creates a new camera description with the given properties.
-      22           0 :   CameraDescription({this.name, this.lensDirection, this.sensorOrientation});
-      23             : 
-      24             :   /// The name of the camera device.
-      25             :   final String name;
-      26             : 
-      27             :   /// The direction the camera is facing.
-      28             :   final CameraLensDirection lensDirection;
-      29             : 
-      30             :   /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.
-      31             :   ///
-      32             :   /// **Range of valid values:**
-      33             :   /// 0, 90, 180, 270
-      34             :   ///
-      35             :   /// On Android, also defines the direction of rolling shutter readout, which
-      36             :   /// is from top to bottom in the sensor's coordinate system.
-      37             :   final int sensorOrientation;
-      38             : 
-      39           0 :   @override
-      40             :   bool operator ==(Object o) {
-      41           0 :     return o is CameraDescription &&
-      42           0 :         o.name == name &&
-      43           0 :         o.lensDirection == lensDirection;
-      44             :   }
-      45             : 
-      46           0 :   @override
-      47             :   int get hashCode {
-      48           0 :     return hashValues(name, lensDirection);
-      49             :   }
-      50             : 
-      51           0 :   @override
-      52             :   String toString() {
-      53           0 :     return '$runtimeType($name, $lensDirection, $sensorOrientation)';
-      54             :   }
-      55             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html deleted file mode 100644 index f3a43cd1a63b..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_event.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html deleted file mode 100644 index f364a44cca63..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_event.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html deleted file mode 100644 index 6b9684af05ce..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_event.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2017 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:ui';
-       6             : 
-       7             : import 'package:meta/meta.dart';
-       8             : 
-       9             : /// Event emitted from the platform implementation.
-      10             : class CameraEvent {
-      11             :   /// Creates an instance of [CameraEvent].
-      12             :   ///
-      13             :   /// The [eventType] argument is required.
-      14             :   ///
-      15             :   /// Depending on the [eventType], the [captureSize], [previewSize] and 
-      16             :   /// [errorDescription] arguments can be null.
-      17           0 :   CameraEvent({
-      18             :     @required this.eventType,
-      19             :     this.captureSize,
-      20             :     this.previewSize,
-      21             :     this.errorDescription,
-      22             :   });
-      23             : 
-      24             :   /// The type of the event.
-      25             :   final CameraEventType eventType;
-      26             : 
-      27             :   /// The capture size in pixels.
-      28             :   /// 
-      29             :   /// Only used if the [eventType] is [CameraEventType.initialized]
-      30             :   final Size captureSize;
-      31             : 
-      32             :   /// The size of the preview in pixels.
-      33             :   /// 
-      34             :   /// Only used if the [eventType] is [CameraEventType.initialized]
-      35             :   final Size previewSize;
-      36             : 
-      37             :   /// Description of the error.
-      38             :   ///
-      39             :   /// Only used if [eventType] is [CameraEventType.error].
-      40             :   final String errorDescription;
-      41             : 
-      42           0 :   @override
-      43             :   bool operator ==(Object other) {
-      44             :     return identical(this, other) ||
-      45           0 :         other is CameraEvent &&
-      46           0 :             runtimeType == other.runtimeType &&
-      47           0 :             eventType == other.eventType &&
-      48           0 :             captureSize == other.captureSize &&
-      49           0 :             previewSize == other.previewSize &&
-      50           0 :             errorDescription == other.errorDescription;
-      51             :   }
-      52             : 
-      53           0 :   @override
-      54             :   int get hashCode =>
-      55           0 :       eventType.hashCode ^
-      56           0 :       captureSize.hashCode ^
-      57           0 :       previewSize.hashCode ^
-      58           0 :       errorDescription.hashCode;
-      59             : }
-      60             : 
-      61             : /// Type of the event.
-      62             : ///
-      63             : /// Emitted by the platform implementation when the camera is closing or
-      64             : /// if an error occured.
-      65           2 : enum CameraEventType {
-      66             :   /// The camera has been initialized.
-      67             : 
-      68             :   /// The camera is closing.
-      69           2 :   cameraClosing,
-      70             : 
-      71             :   /// An error occured while accessing the camera.
-      72           2 :   error,
-      73             : 
-      74             :   /// An unknown event has been received.
-      75           2 :   unknown,
-      76             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html deleted file mode 100644 index 5cf21835a9fc..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_image.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html deleted file mode 100644 index 945e78a95d84..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_image.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html deleted file mode 100644 index 96ab1f424ec4..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - LCOV - lcov.info - types/camera_image.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:typed_data';
-       6             : 
-       7             : import 'package:flutter/foundation.dart';
-       8             : 
-       9             : /// A single color plane of image data.
-      10             : ///
-      11             : /// The number and meaning of the planes in an image are determined by the
-      12             : /// format of the Image.
-      13             : class Plane {
-      14           1 :   Plane._fromPlatformData(Map<dynamic, dynamic> data)
-      15           1 :       : bytes = data['bytes'],
-      16           1 :         bytesPerPixel = data['bytesPerPixel'],
-      17           1 :         bytesPerRow = data['bytesPerRow'],
-      18           1 :         height = data['height'],
-      19           1 :         width = data['width'];
-      20             : 
-      21             :   /// Bytes representing this plane.
-      22             :   final Uint8List bytes;
-      23             : 
-      24             :   /// The distance between adjacent pixel samples on Android, in bytes.
-      25             :   ///
-      26             :   /// Will be `null` on iOS.
-      27             :   final int bytesPerPixel;
-      28             : 
-      29             :   /// The row stride for this color plane, in bytes.
-      30             :   final int bytesPerRow;
-      31             : 
-      32             :   /// Height of the pixel buffer on iOS.
-      33             :   ///
-      34             :   /// Will be `null` on Android
-      35             :   final int height;
-      36             : 
-      37             :   /// Width of the pixel buffer on iOS.
-      38             :   ///
-      39             :   /// Will be `null` on Android.
-      40             :   final int width;
-      41             : }
-      42             : 
-      43             : // TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values.
-      44             : /// Group of image formats that are comparable across Android and iOS platforms.
-      45           2 : enum ImageFormatGroup {
-      46             :   /// The image format does not fit into any specific group.
-      47           2 :   unknown,
-      48             : 
-      49             :   /// Multi-plane YUV 420 format.
-      50             :   ///
-      51             :   /// This format is a generic YCbCr format, capable of describing any 4:2:0
-      52             :   /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
-      53             :   /// with 8 bits per color sample.
-      54             :   ///
-      55             :   /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See
-      56             :   /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
-      57             :   ///
-      58             :   /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See
-      59             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc
-      60           2 :   yuv420,
-      61             : 
-      62             :   /// 32-bit BGRA.
-      63             :   ///
-      64             :   /// On iOS, this is `kCVPixelFormatType_32BGRA`. See
-      65             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc
-      66           2 :   bgra8888,
-      67             : }
-      68             : 
-      69             : /// Describes how pixels are represented in an image.
-      70             : class ImageFormat {
-      71           2 :   ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
-      72             : 
-      73             :   /// Describes the format group the raw image format falls into.
-      74             :   final ImageFormatGroup group;
-      75             : 
-      76             :   /// Raw version of the format from the Android or iOS platform.
-      77             :   ///
-      78             :   /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
-      79             :   /// https://developer.android.com/reference/android/graphics/ImageFormat
-      80             :   ///
-      81             :   /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
-      82             :   /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
-      83             :   final dynamic raw;
-      84             : }
-      85             : 
-      86           1 : ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
-      87           2 :   if (defaultTargetPlatform == TargetPlatform.android) {
-      88             :     // android.graphics.ImageFormat.YUV_420_888
-      89           1 :     if (rawFormat == 35) {
-      90             :       return ImageFormatGroup.yuv420;
-      91             :     }
-      92             :   }
-      93             : 
-      94           2 :   if (defaultTargetPlatform == TargetPlatform.iOS) {
-      95             :     switch (rawFormat) {
-      96             :       // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
-      97           1 :       case 875704438:
-      98             :         return ImageFormatGroup.yuv420;
-      99             :       // kCVPixelFormatType_32BGRA
-     100           1 :       case 1111970369:
-     101             :         return ImageFormatGroup.bgra8888;
-     102             :     }
-     103             :   }
-     104             : 
-     105             :   return ImageFormatGroup.unknown;
-     106             : }
-     107             : 
-     108             : /// A single complete image buffer from the platform camera.
-     109             : ///
-     110             : /// This class allows for direct application access to the pixel data of an
-     111             : /// Image through one or more [Uint8List]. Each buffer is encapsulated in a
-     112             : /// [Plane] that describes the layout of the pixel data in that plane. The
-     113             : /// [CameraImage] is not directly usable as a UI resource.
-     114             : ///
-     115             : /// Although not all image formats are planar on iOS, we treat 1-dimensional
-     116             : /// images as single planar images.
-     117             : class CameraImage {
-     118             :   /// Creates a new [CameraImage] from the data received from the platform.
-     119           1 :   CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
-     120           2 :       : format = ImageFormat._fromPlatformData(data['format']),
-     121           1 :         height = data['height'],
-     122           1 :         width = data['width'],
-     123           2 :         planes = List<Plane>.unmodifiable(data['planes']
-     124           3 :             .map((dynamic planeData) => Plane._fromPlatformData(planeData)));
-     125             : 
-     126             :   /// Format of the image provided.
-     127             :   ///
-     128             :   /// Determines the number of planes needed to represent the image, and
-     129             :   /// the general layout of the pixel data in each [Uint8List].
-     130             :   final ImageFormat format;
-     131             : 
-     132             :   /// Height of the image in pixels.
-     133             :   ///
-     134             :   /// For formats where some color channels are subsampled, this is the height
-     135             :   /// of the largest-resolution plane.
-     136             :   final int height;
-     137             : 
-     138             :   /// Width of the image in pixels.
-     139             :   ///
-     140             :   /// For formats where some color channels are subsampled, this is the width
-     141             :   /// of the largest-resolution plane.
-     142             :   final int width;
-     143             : 
-     144             :   /// The pixels planes for this image.
-     145             :   ///
-     146             :   /// The number of planes is determined by the format of the image.
-     147             :   final List<Plane> planes;
-     148             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html deleted file mode 100644 index 5cf500911692..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - LCOV - lcov.info - types - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html deleted file mode 100644 index 913571e880cb..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - LCOV - lcov.info - types - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index.html b/packages/camera/camera_platform_interface/coverage/html/types/index.html deleted file mode 100644 index 479a3edaa376..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - LCOV - lcov.info - types - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html deleted file mode 100644 index fdf79712000a..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/resolution_preset.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html deleted file mode 100644 index 40ed9b1de628..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - LCOV - lcov.info - types/resolution_preset.dart - functions - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html deleted file mode 100644 index 05c40ae95295..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - LCOV - lcov.info - types/resolution_preset.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : /// Affect the quality of video recording and image capture:
-       2             : ///
-       3             : /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically.
-       4           2 : enum ResolutionPreset {
-       5             :   /// 352x288 on iOS, 240p (320x240) on Android
-       6           2 :   low,
-       7             : 
-       8             :   /// 480p (640x480 on iOS, 720x480 on Android)
-       9           2 :   medium,
-      10             : 
-      11             :   /// 720p (1280x720)
-      12           2 :   high,
-      13             : 
-      14             :   /// 1080p (1920x1080)
-      15           2 :   veryHigh,
-      16             : 
-      17             :   /// 2160p (3840x2160)
-      18           2 :   ultraHigh,
-      19             : 
-      20             :   /// The highest resolution available.
-      21           2 :   max,
-      22             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/updown.png b/packages/camera/camera_platform_interface/coverage/html/updown.png deleted file mode 100644 index aa56a238b3e6c435265250f9266cd1b8caba0f20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ diff --git a/packages/camera/camera_platform_interface/coverage/lcov.info b/packages/camera/camera_platform_interface/coverage/lcov.info deleted file mode 100644 index f8be39954cf2..000000000000 --- a/packages/camera/camera_platform_interface/coverage/lcov.info +++ /dev/null @@ -1,110 +0,0 @@ -SF:lib/src/types/camera_image.dart -DA:14,1 -DA:15,1 -DA:16,1 -DA:17,1 -DA:18,1 -DA:19,1 -DA:45,2 -DA:47,2 -DA:60,2 -DA:66,2 -DA:71,2 -DA:86,1 -DA:87,2 -DA:89,1 -DA:94,2 -DA:97,1 -DA:100,1 -DA:119,1 -DA:120,2 -DA:121,1 -DA:122,1 -DA:123,2 -DA:124,3 -LF:23 -LH:23 -end_of_record -SF:lib/src/platform_interface/camera_platform.dart -DA:21,3 -DA:23,3 -DA:25,3 -DA:30,2 -DA:34,1 -DA:35,2 -DA:40,1 -DA:41,1 -DA:45,1 -DA:50,1 -DA:54,1 -DA:55,1 -DA:59,1 -DA:60,1 -DA:64,1 -DA:65,1 -DA:76,1 -DA:77,1 -DA:81,1 -DA:82,1 -DA:86,1 -DA:87,1 -DA:91,1 -DA:92,1 -DA:102,1 -DA:103,1 -DA:107,1 -DA:108,1 -DA:112,1 -DA:113,1 -LF:30 -LH:30 -end_of_record -SF:lib/src/types/camera_description.dart -DA:8,2 -DA:10,2 -DA:13,2 -DA:16,2 -DA:22,0 -DA:39,0 -DA:41,0 -DA:42,0 -DA:43,0 -DA:46,0 -DA:48,0 -DA:51,0 -DA:53,0 -LF:13 -LH:4 -end_of_record -SF:lib/src/types/camera_event.dart -DA:17,0 -DA:42,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:49,0 -DA:50,0 -DA:53,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:65,2 -DA:69,2 -DA:72,2 -DA:75,2 -LF:17 -LH:4 -end_of_record -SF:lib/src/types/resolution_preset.dart -DA:4,2 -DA:6,2 -DA:9,2 -DA:12,2 -DA:15,2 -DA:18,2 -DA:21,2 -LF:7 -LH:7 -end_of_record From 7b4c8be6dea0fdeba148a4bd40248f1d12f182dd Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 9 Nov 2020 17:03:55 +0100 Subject: [PATCH 03/67] Renamed onLatestImageAvailableHandler definition --- .../camera_platform_interface/lib/src/types/callbacks.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart index edef7929a761..8cf3a00bc245 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart @@ -8,4 +8,4 @@ import 'types.dart'; /// /// This is used by [CameraPlatform.startImageStream]. // ignore: inference_failure_on_function_return_type -typedef onLatestImageAvailable = Function(CameraImage image); +typedef ImageAvailableHandler = Function(CameraImage image); From a959bbe88ed1193f14aa7545cd66552b90bcf269 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 12 Nov 2020 11:37:41 +0100 Subject: [PATCH 04/67] Split CameraEvents into separate streams --- .../lib/src/types/callbacks.dart | 11 --- .../lib/src/types/camera_event.dart | 76 ------------------- 2 files changed, 87 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/callbacks.dart delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_event.dart diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart deleted file mode 100644 index 8cf3a00bc245..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'types.dart'; - -/// Signature for a callback receiving the a camera image. -/// -/// This is used by [CameraPlatform.startImageStream]. -// ignore: inference_failure_on_function_return_type -typedef ImageAvailableHandler = Function(CameraImage image); diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart deleted file mode 100644 index 7b9b47381e28..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui'; - -import 'package:meta/meta.dart'; - -/// Event emitted from the platform implementation. -class CameraEvent { - /// Creates an instance of [CameraEvent]. - /// - /// The [eventType] argument is required. - /// - /// Depending on the [eventType], the [captureSize], [previewSize] and - /// [errorDescription] arguments can be null. - CameraEvent({ - @required this.eventType, - this.captureSize, - this.previewSize, - this.errorDescription, - }); - - /// The type of the event. - final CameraEventType eventType; - - /// The capture size in pixels. - /// - /// Only used if the [eventType] is [CameraEventType.initialized] - final Size captureSize; - - /// The size of the preview in pixels. - /// - /// Only used if the [eventType] is [CameraEventType.initialized] - final Size previewSize; - - /// Description of the error. - /// - /// Only used if [eventType] is [CameraEventType.error]. - final String errorDescription; - - @override - bool operator ==(Object other) { - return identical(this, other) || - other is CameraEvent && - runtimeType == other.runtimeType && - eventType == other.eventType && - captureSize == other.captureSize && - previewSize == other.previewSize && - errorDescription == other.errorDescription; - } - - @override - int get hashCode => - eventType.hashCode ^ - captureSize.hashCode ^ - previewSize.hashCode ^ - errorDescription.hashCode; -} - -/// Type of the event. -/// -/// Emitted by the platform implementation when the camera is closing or -/// if an error occured. -enum CameraEventType { - /// The camera has been initialized. - - /// The camera is closing. - cameraClosing, - - /// An error occured while accessing the camera. - error, - - /// An unknown event has been received. - unknown, -} From 2fa0419ca39e44df68030da18c342898ce996a73 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:26:40 +0100 Subject: [PATCH 05/67] Implemented & tested first parts of method channel implementation --- .../test/camera_platform_interface_test.dart | 2 +- .../test/utils/event_channel_mock.dart | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 7a6fc344503f..903944787373 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -129,7 +129,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.initializeCamera(null), + () => cameraPlatform.initializeCamera(null, null), throwsUnimplementedError, ); }); diff --git a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart new file mode 100644 index 000000000000..4bef6379ff80 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; + +class EventChannelMock { + final MethodChannel _methodChannel; + final Stream stream; + final log = []; + + StreamSubscription _streamSubscription; + + EventChannelMock({ + @required String channelName, + @required this.stream, + }) : _methodChannel = MethodChannel(channelName) { + _methodChannel.setMockMethodCallHandler(_handler); + } + + Future _handler(MethodCall methodCall) { + log.add(methodCall); + + switch (methodCall.method) { + case 'listen': + _onListen(); + break; + case 'cancel': + _onCancel(); + break; + default: + return null; + } + + return Future.value(); + } + + void _onListen() { + _streamSubscription = stream.handleError((error) { + ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + _methodChannel.name, + _createErrorEnvelope(error), + (_) {}, + ); + }).listen((event) { + ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + _methodChannel.name, + _createSuccessEnvelope(event), + (_) {}, + ); + }); + } + + void _onCancel() { + if (_streamSubscription != null) { + _streamSubscription.cancel(); + } + } + + ByteData _createErrorEnvelope(Exception error) { + var code = "UNKNOWN_EXCEPTION"; + String message; + dynamic details; + + if (error is PlatformException) { + code = error.code; + message = error.message; + details = error.details; + } + + return const StandardMethodCodec() + .encodeErrorEnvelope(code: code, message: message, details: details); + } + + ByteData _createSuccessEnvelope(dynamic event) { + return const StandardMethodCodec().encodeSuccessEnvelope(event); + } +} \ No newline at end of file From ef6dee8e8ae2d38b67705a89fad1e33db20b7bcc Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:29:36 +0100 Subject: [PATCH 06/67] Remove unused EventChannelMock class --- .../test/utils/event_channel_mock.dart | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart diff --git a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart deleted file mode 100644 index 4bef6379ff80..000000000000 --- a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; - -class EventChannelMock { - final MethodChannel _methodChannel; - final Stream stream; - final log = []; - - StreamSubscription _streamSubscription; - - EventChannelMock({ - @required String channelName, - @required this.stream, - }) : _methodChannel = MethodChannel(channelName) { - _methodChannel.setMockMethodCallHandler(_handler); - } - - Future _handler(MethodCall methodCall) { - log.add(methodCall); - - switch (methodCall.method) { - case 'listen': - _onListen(); - break; - case 'cancel': - _onCancel(); - break; - default: - return null; - } - - return Future.value(); - } - - void _onListen() { - _streamSubscription = stream.handleError((error) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - _methodChannel.name, - _createErrorEnvelope(error), - (_) {}, - ); - }).listen((event) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - _methodChannel.name, - _createSuccessEnvelope(event), - (_) {}, - ); - }); - } - - void _onCancel() { - if (_streamSubscription != null) { - _streamSubscription.cancel(); - } - } - - ByteData _createErrorEnvelope(Exception error) { - var code = "UNKNOWN_EXCEPTION"; - String message; - dynamic details; - - if (error is PlatformException) { - code = error.code; - message = error.message; - details = error.details; - } - - return const StandardMethodCodec() - .encodeErrorEnvelope(code: code, message: message, details: details); - } - - ByteData _createSuccessEnvelope(dynamic event) { - return const StandardMethodCodec().encodeSuccessEnvelope(event); - } -} \ No newline at end of file From c15df6c83d42b3cd67ef3dd554c02a147c65c030 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:32:58 +0100 Subject: [PATCH 07/67] Add missing unit tests --- .../test/method_channel_camera_test.dart | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 packages/camera/camera_platform_interface/test/method_channel_camera_test.dart diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart new file mode 100644 index 000000000000..49f037c36c7b --- /dev/null +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -0,0 +1,180 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils/method_channel_mock.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelCamera', () { + group('Initialization & Disposal Tests', () { + test('Should receive a camera id when initialized', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1} + }); + final camera = MethodChannelCamera(); + + // Act + final cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + print('cameraId $cameraId'); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + isMethodCall( + 'initialize', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': null + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + 'dispose': {'textureId': 1} + }); + + final camera = MethodChannelCamera(); + final cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + isNotNull, + isMethodCall( + 'dispose', + arguments: {'textureId': 1}, + ), + ]); + }); + }); + + group('Interaction Tests', () { + MethodChannelMock channel; + MethodChannelCamera camera; + int cameraId; + setUp(() async { + channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + }); + + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onResolutionChanged(cameraId); + final streamQueue = StreamQueue(resolutionStream); + + // Emit test events + final fhdEvent = + ResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); + final uhdEvent = + ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + // + // // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final streamQueue = StreamQueue(eventStream); + + // Emit test events + final event = CameraClosingEvent(cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final errorStream = camera.onCameraError(cameraId); + final streamQueue = StreamQueue(errorStream); + + // Emit test events + final event = CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + }); +} From 8e33d0e135fcfe279ed86b945bcc310b1e21e71c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 14:16:28 +0100 Subject: [PATCH 08/67] Added placeholders in default method channel implementation --- .../lib/src/method_channel/method_channel_camera.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index bc836b0ac98e..150bbd6038eb 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -33,6 +33,7 @@ class MethodChannelCamera extends CameraPlatform { cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); + // TODO(BeMacized): Write unit tests. @override Future> availableCameras() async { try { From a9ea2b95564917fb5d52fefc292913c1c2d6c82c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 13:54:06 +0100 Subject: [PATCH 09/67] Updated platform interface --- .../lib/src/platform_interface/camera_platform.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index c398e9e9ef17..6583124213a2 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -93,8 +93,8 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stops the video recording and returns the file where it was saved. - Future stopVideoRecording(int cameraId) { + /// Stops the video recording. + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 62c9a8c7f2e9a8cf210f7cebbb4a6b0ccaf320d2 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 14:10:57 +0100 Subject: [PATCH 10/67] Update packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart Co-authored-by: Maurits van Beusekom --- .../lib/src/platform_interface/camera_platform.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 6583124213a2..59c387718fa5 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -94,6 +94,10 @@ abstract class CameraPlatform extends PlatformInterface { } /// Stops the video recording. + /// + /// When the [stopVideoRecording] method completes successfully the recorded + /// video can be accessed through the file returned by the + /// [startVideoRecording] method. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From ca7f5d77b4fca87d2b3112203db2c1091aa3a08f Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:14:10 +0100 Subject: [PATCH 11/67] Add unit test for availableCameras --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 51 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 150bbd6038eb..bc836b0ac98e 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -33,7 +33,6 @@ class MethodChannelCamera extends CameraPlatform { cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); - // TODO(BeMacized): Write unit tests. @override Future> availableCameras() async { try { diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 49f037c36c7b..621b21120ff3 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -77,12 +77,11 @@ void main() { }); }); - group('Interaction Tests', () { - MethodChannelMock channel; + group('Event Tests', () { MethodChannelCamera camera; int cameraId; setUp(() async { - channel = MethodChannelMock( + MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'initialize': {'textureId': 1}, @@ -176,5 +175,51 @@ void main() { await streamQueue.cancel(); }); }); + + group('Function Tests', () { + MethodChannelCamera camera; + int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + }); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + List> returnData = [ + {'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1}, + {'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2} + ]; + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + List cameras = await camera.availableCameras(); + + // Assert + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name'], + lensDirection: + camera.parseCameraLensDirection(returnData[i]['lensFacing']), + sensorOrientation: returnData[i]['sensorOrientation'], + ); + expect(cameras[i], cameraDescription); + } + }); + }); }); } From eff917fb64c0376b43b27381ef2fe1259d0919c7 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:25:30 +0100 Subject: [PATCH 12/67] Expand availableCameras unit test. Added unit test for takePicture. --- .../test/method_channel_camera_test.dart | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 621b21120ff3..9353a5fa6ae1 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -200,7 +201,7 @@ void main() { {'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1}, {'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2} ]; - MethodChannelMock( + MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'availableCameras': returnData}, ); @@ -209,6 +210,9 @@ void main() { List cameras = await camera.availableCameras(); // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { CameraDescription cameraDescription = CameraDescription( @@ -220,6 +224,22 @@ void main() { expect(cameras[i], cameraDescription); } }); + + test('Should take a picture and return an XFile instance', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: {'textureId': cameraId}), + ]); + expect(file.path, '/test/path.jpg'); + }); }); }); } From afd02f75f083f8603538d714a0c48e4163044b96 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:47:39 +0100 Subject: [PATCH 13/67] Add unit test for startVideoRecording --- .../test/method_channel_camera_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 9353a5fa6ae1..e5752753af32 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -240,6 +240,24 @@ void main() { ]); expect(file.path, '/test/path.jpg'); }); + + test('Should start recording a video and return an XFile instance', + () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': '/test/path.mkv'}); + + // Act + XFile file = await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', + arguments: {'textureId': cameraId}), + ]); + expect(file.path, '/test/path.mkv'); + }); }); }); } From 397acce4177a4745f60ddb0455aefa187dd2c74a Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:49:47 +0100 Subject: [PATCH 14/67] Add unit test for prepareForVideoRecording --- .../test/method_channel_camera_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e5752753af32..924a639f2f16 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -241,6 +241,22 @@ void main() { expect(file.path, '/test/path.jpg'); }); + test('Should prepare for video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); + + // Act + await camera.prepareForVideoRecording(); + + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); + test('Should start recording a video and return an XFile instance', () async { // Arrange From 39b4cd5eaa3ba409b02e2d48e93d1c24378dac34 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:51:33 +0100 Subject: [PATCH 15/67] Add unit test for stopVideoRecording --- .../test/method_channel_camera_test.dart | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 924a639f2f16..360c953e7808 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -236,7 +236,9 @@ void main() { // Assert expect(channel.log, [ - isMethodCall('takePicture', arguments: {'textureId': cameraId}), + isMethodCall('takePicture', arguments: { + 'textureId': cameraId, + }), ]); expect(file.path, '/test/path.jpg'); }); @@ -269,11 +271,30 @@ void main() { // Assert expect(channel.log, [ - isMethodCall('startVideoRecording', - arguments: {'textureId': cameraId}), + isMethodCall('startVideoRecording', arguments: { + 'textureId': cameraId, + }), ]); expect(file.path, '/test/path.mkv'); }); + + test('Should stop a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': null}, + ); + + // Act + await camera.stopVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); }); }); } From a999954c2c47d984bc815e9aba144c50587f95a6 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:52:22 +0100 Subject: [PATCH 16/67] Add unit test for pauseVideoRecording --- .../test/method_channel_camera_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 360c953e7808..5f6ec85ebf4f 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -295,6 +295,24 @@ void main() { }), ]); }); + + test('Should pause a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); + + // Act + await camera.pauseVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); }); }); } From 451500a161f22cf916800e50c4d7a78d30b7abba Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:56:00 +0100 Subject: [PATCH 17/67] Add unit test for buildView --- .../test/method_channel_camera_test.dart | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 5f6ec85ebf4f..e715e802b5dc 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -9,6 +9,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'utils/method_channel_mock.dart'; @@ -313,6 +314,33 @@ void main() { }), ]); }); + + test('Should resume a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); + + // Act + await camera.resumeVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); + + test('Should build a texture widget as view widget', () async { + // Act + Widget widget = camera.buildView(cameraId); + + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); }); }); } From 5bf7795536b7aefb9d42fcfa10cefe33c01d37d8 Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 19 Nov 2020 09:59:01 +0100 Subject: [PATCH 18/67] WIP: Dart and Android implementation --- .../flutter/plugins/camera/DartMessenger.java | 4 +- .../example/integration_test/camera_test.dart | 1 + packages/camera/camera/example/lib/main.dart | 64 +++--- packages/camera/camera/lib/camera.dart | 205 +++--------------- packages/camera/camera/pubspec.yaml | 2 + 5 files changed, 61 insertions(+), 215 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index fe385bef7818..08b36cb2cb87 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -16,7 +16,7 @@ enum EventType { } DartMessenger(BinaryMessenger messenger, long eventChannelId) { - new EventChannel(messenger, "flutter.io/cameraPlugin/cameraEvents" + eventChannelId) + new EventChannel(messenger, "flutter.io/cameraPlugin/camera" + eventChannelId) .setStreamHandler( new EventChannel.StreamHandler() { @Override @@ -44,7 +44,7 @@ void send(EventType eventType, @Nullable String description) { event.put("eventType", eventType.toString().toLowerCase()); // Only errors have a description. if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { - event.put("errorDescription", description); + event.put("description", description); } eventSink.success(event); } diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index ef4646f5ced9..146126963632 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; +import 'package:camera/camera.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:camera/camera.dart'; diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 3ec6604ad788..a23135f64f71 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -38,8 +38,8 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { CameraController controller; - String imagePath; - String videoPath; + XFile imageFile; + XFile videoFile; VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; @@ -130,9 +130,9 @@ class _CameraExampleHomeState extends State ), ); } else { - return AspectRatio( - aspectRatio: controller.value.aspectRatio, - child: CameraPreview(controller), + return Container( + // aspectRatio: controller.value.aspectRatio, + child: controller.buildView(), ); } } @@ -166,11 +166,11 @@ class _CameraExampleHomeState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - videoController == null && imagePath == null + videoController == null && imageFile == null ? Container() : SizedBox( child: (videoController == null) - ? Image.file(File(imagePath)) + ? Image.file(File(imageFile.path)) : Container( child: Center( child: AspectRatio( @@ -306,29 +306,29 @@ class _CameraExampleHomeState extends State } void onTakePictureButtonPressed() { - takePicture().then((String filePath) { + takePicture().then((XFile file) { if (mounted) { setState(() { - imagePath = filePath; + imageFile = file; videoController?.dispose(); videoController = null; }); - if (filePath != null) showInSnackBar('Picture saved to $filePath'); + if (file != null) showInSnackBar('Picture saved to ${file.path}'); } }); } void onVideoRecordButtonPressed() { - startVideoRecording().then((String filePath) { + startVideoRecording().then((XFile file) { if (mounted) setState(() {}); - if (filePath != null) showInSnackBar('Saving video to $filePath'); + if (file != null) showInSnackBar('Saving video to ${file.path}'); }); } void onStopButtonPressed() { stopVideoRecording().then((_) { if (mounted) setState(() {}); - showInSnackBar('Video recorded to: $videoPath'); + showInSnackBar('Video recorded to: ${videoFile.path}'); }); } @@ -346,30 +346,24 @@ class _CameraExampleHomeState extends State }); } - Future startVideoRecording() async { + Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Movies/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.mp4'; - if (controller.value.isRecordingVideo) { // A recording is already started, do nothing. return null; } try { - videoPath = filePath; - await controller.startVideoRecording(filePath); + XFile file = await controller.startVideoRecording(); + return file; } on CameraException catch (e) { _showCameraException(e); return null; } - return filePath; } Future stopVideoRecording() async { @@ -414,8 +408,8 @@ class _CameraExampleHomeState extends State } Future _startVideoPlayer() async { - final VideoPlayerController vcontroller = - VideoPlayerController.file(File(videoPath)); + final VideoPlayerController vController = + VideoPlayerController.file(File(videoFile.path)); videoPlayerListener = () { if (videoController != null && videoController.value.size != null) { // Refreshing the state to update video player with the correct ratio. @@ -423,28 +417,24 @@ class _CameraExampleHomeState extends State videoController.removeListener(videoPlayerListener); } }; - vcontroller.addListener(videoPlayerListener); - await vcontroller.setLooping(true); - await vcontroller.initialize(); + vController.addListener(videoPlayerListener); + await vController.setLooping(true); + await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { - imagePath = null; - videoController = vcontroller; + imageFile = null; + videoController = vController; }); } - await vcontroller.play(); + await vController.play(); } - Future takePicture() async { + Future takePicture() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Pictures/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.jpg'; if (controller.value.isTakingPicture) { // A capture is already pending, do nothing. @@ -452,12 +442,12 @@ class _CameraExampleHomeState extends State } try { - await controller.takePicture(filePath); + XFile file = await controller.takePicture(); + return file; } on CameraException catch (e) { _showCameraException(e); return null; } - return filePath; } void _showCameraException(CameraException e) { diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 3b2cd77c5757..551efd65139a 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -5,141 +5,28 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +export 'package:camera_platform_interface/camera_platform_interface.dart'; + part 'camera_image.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); -/// The direction the camera is facing. -enum CameraLensDirection { - /// Front facing camera (a user looking at the screen is seen by the camera). - front, - - /// Back facing camera (a user looking at the screen is not seen by the camera). - back, - - /// External camera which may not be mounted to the device. - external, -} - -/// Affect the quality of video recording and image capture: -/// -/// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. -enum ResolutionPreset { - /// 352x288 on iOS, 240p (320x240) on Android - low, - - /// 480p (640x480 on iOS, 720x480 on Android) - medium, - - /// 720p (1280x720) - high, - - /// 1080p (1920x1080) - veryHigh, - - /// 2160p (3840x2160) - ultraHigh, - - /// The highest resolution available. - max, -} - /// Signature for a callback receiving the a camera image. /// /// This is used by [CameraController.startImageStream]. // ignore: inference_failure_on_function_return_type typedef onLatestImageAvailable = Function(CameraImage image); -/// Returns the resolution preset as a String. -String serializeResolutionPreset(ResolutionPreset resolutionPreset) { - switch (resolutionPreset) { - case ResolutionPreset.max: - return 'max'; - case ResolutionPreset.ultraHigh: - return 'ultraHigh'; - case ResolutionPreset.veryHigh: - return 'veryHigh'; - case ResolutionPreset.high: - return 'high'; - case ResolutionPreset.medium: - return 'medium'; - case ResolutionPreset.low: - return 'low'; - } - throw ArgumentError('Unknown ResolutionPreset value'); -} - -CameraLensDirection _parseCameraLensDirection(String string) { - switch (string) { - case 'front': - return CameraLensDirection.front; - case 'back': - return CameraLensDirection.back; - case 'external': - return CameraLensDirection.external; - } - throw ArgumentError('Unknown CameraLensDirection value'); -} - /// Completes with a list of available cameras. /// /// May throw a [CameraException]. Future> availableCameras() async { - try { - final List> cameras = await _channel - .invokeListMethod>('availableCameras'); - return cameras.map((Map camera) { - return CameraDescription( - name: camera['name'], - lensDirection: _parseCameraLensDirection(camera['lensFacing']), - sensorOrientation: camera['sensorOrientation'], - ); - }).toList(); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } -} - -/// Properties of a camera device. -class CameraDescription { - /// Creates a new camera description with the given properties. - CameraDescription({this.name, this.lensDirection, this.sensorOrientation}); - - /// The name of the camera device. - final String name; - - /// The direction the camera is facing. - final CameraLensDirection lensDirection; - - /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation. - /// - /// **Range of valid values:** - /// 0, 90, 180, 270 - /// - /// On Android, also defines the direction of rolling shutter readout, which - /// is from top to bottom in the sensor's coordinate system. - final int sensorOrientation; - - @override - bool operator ==(Object o) { - return o is CameraDescription && - o.name == name && - o.lensDirection == lensDirection; - } - - @override - int get hashCode { - return hashValues(name, lensDirection); - } - - @override - String toString() { - return '$runtimeType($name, $lensDirection, $sensorOrientation)'; - } + return CameraPlatform.instance.availableCameras(); } /// This is thrown when the plugin reports an error. @@ -170,7 +57,7 @@ class CameraPreview extends StatelessWidget { @override Widget build(BuildContext context) { return controller.value.isInitialized - ? Texture(textureId: controller._textureId) + ? Texture(textureId: controller._cameraId) : Container(); } } @@ -188,7 +75,7 @@ class CameraValue { bool isRecordingPaused, }) : _isRecordingPaused = isRecordingPaused; - /// Creates a new camera controller state for an uninitialzed controller. + /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, @@ -301,7 +188,7 @@ class CameraController extends ValueNotifier { /// Whether to include audio when recording a video. final bool enableAudio; - int _textureId; + int _cameraId; bool _isDisposed = false; StreamSubscription _eventSubscription; StreamSubscription _imageStreamSubscription; @@ -323,28 +210,17 @@ class CameraController extends ValueNotifier { } try { _creatingCompleter = Completer(); - final Map reply = - await _channel.invokeMapMethod( - 'initialize', - { - 'cameraName': description.name, - 'resolutionPreset': serializeResolutionPreset(resolutionPreset), - 'enableAudio': enableAudio, - }, - ); - _textureId = reply['textureId']; - value = value.copyWith( - isInitialized: true, - previewSize: Size( - reply['previewWidth'].toDouble(), - reply['previewHeight'].toDouble(), - ), - ); + + _cameraId = await CameraPlatform.instance.initializeCamera( + description, resolutionPreset, + enableAudio: enableAudio); + + value = value.copyWith(isInitialized: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } _eventSubscription = - EventChannel('flutter.io/cameraPlugin/cameraEvents$_textureId') + EventChannel('flutter.io/cameraPlugin/camera$_cameraId') .receiveBroadcastStream() .listen(_listener); _creatingCompleter.complete(); @@ -363,13 +239,15 @@ class CameraController extends ValueNotifier { /// /// Throws a [CameraException] if the prepare fails. Future prepareForVideoRecording() async { - await _channel.invokeMethod('prepareForVideoRecording'); + await CameraPlatform.instance.prepareForVideoRecording(); } /// Listen to events from the native plugins. /// /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. void _listener(dynamic event) { + //TODO: Replace + debugPrint("event is $event"); final Map map = event; if (_isDisposed) { return; @@ -394,7 +272,7 @@ class CameraController extends ValueNotifier { /// The file can be read as this function returns. /// /// Throws a [CameraException] if the capture fails. - Future takePicture(String path) async { + Future takePicture() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController.', @@ -409,11 +287,9 @@ class CameraController extends ValueNotifier { } try { value = value.copyWith(isTakingPicture: true); - await _channel.invokeMethod( - 'takePicture', - {'textureId': _textureId, 'path': path}, - ); + XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); + return file; } on PlatformException catch (e) { value = value.copyWith(isTakingPicture: false); throw CameraException(e.code, e.message); @@ -433,7 +309,7 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if image streaming or video recording has /// already started. // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) async { + Widget buildView() { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -454,19 +330,10 @@ class CameraController extends ValueNotifier { } try { - await _channel.invokeMethod('startImageStream'); - value = value.copyWith(isStreamingImages: true); + return CameraPlatform.instance.buildView(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( - (dynamic imageData) { - onAvailable(CameraImage._fromPlatformData(imageData)); - }, - ); } /// Stop streaming images from platform camera. @@ -514,7 +381,7 @@ class CameraController extends ValueNotifier { /// The file can be read as soon as [stopVideoRecording] returns. /// /// Throws a [CameraException] if the capture fails. - Future startVideoRecording(String filePath) async { + Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -535,11 +402,9 @@ class CameraController extends ValueNotifier { } try { - await _channel.invokeMethod( - 'startVideoRecording', - {'textureId': _textureId, 'filePath': filePath}, - ); + XFile file = await CameraPlatform.instance.startVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -560,11 +425,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: false); - await _channel.invokeMethod( - 'stopVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -587,11 +449,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); - await _channel.invokeMethod( - 'pauseVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -614,11 +473,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); - await _channel.invokeMethod( - 'resumeVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -634,10 +490,7 @@ class CameraController extends ValueNotifier { super.dispose(); if (_creatingCompleter != null) { await _creatingCompleter.future; - await _channel.invokeMethod( - 'dispose', - {'textureId': _textureId}, - ); + await CameraPlatform.instance.dispose(_cameraId); await _eventSubscription?.cancel(); } } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 146219d8366f..705114d06f38 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,6 +8,8 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter + camera_platform_interface: + path: ../camera_platform_interface dev_dependencies: path_provider: ^0.5.0 From 78b4db0b073c738cfab07080c97cb299f7c05060 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 13 Nov 2020 14:36:59 +0100 Subject: [PATCH 19/67] Fix formatting --- .../lib/src/platform_interface/camera_platform.dart | 2 +- .../test/camera_platform_interface_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 59c387718fa5..4b62842e30b4 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -96,7 +96,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Stops the video recording. /// /// When the [stopVideoRecording] method completes successfully the recorded - /// video can be accessed through the file returned by the + /// video can be accessed through the file returned by the /// [startVideoRecording] method. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 903944787373..7a6fc344503f 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -129,7 +129,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.initializeCamera(null, null), + () => cameraPlatform.initializeCamera(null), throwsUnimplementedError, ); }); From 8afa33a27844815010d0e8599b11550654c42e8b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 19 Nov 2020 11:10:28 +0100 Subject: [PATCH 20/67] Have resolution stream replay last value on subscription. Replace stream_transform with rxdart. --- .../lib/src/method_channel/method_channel_camera.dart | 4 +++- .../test/method_channel_camera_test.dart | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index bc836b0ac98e..75a736fc14de 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -10,7 +10,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:stream_transform/stream_transform.dart'; +import 'package:rxdart/rxdart.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); @@ -29,6 +29,8 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); + final Map _cameraResolutionChangedEventStreams = {}; + Stream _events(int cameraId) => cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e715e802b5dc..f0ac45c239ec 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -33,7 +33,6 @@ void main() { CameraDescription(name: 'Test'), ResolutionPreset.high, ); - print('cameraId $cameraId'); // Assert expect(cameraId, 1); @@ -334,7 +333,7 @@ void main() { }); test('Should build a texture widget as view widget', () async { - // Act + // Act Widget widget = camera.buildView(cameraId); // Act From d3411ba9fb873dd96051141a8f29ab43fbdd819b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 19 Nov 2020 13:41:56 +0100 Subject: [PATCH 21/67] Added reverse method channel to replace event channel. Updated initialise and takePicture implementations for android. WIP implementation for startVideoRecording --- .../io/flutter/plugins/camera/Camera.java | 900 +++++++++--------- .../flutter/plugins/camera/DartMessenger.java | 84 +- .../plugins/camera/MethodCallHandlerImpl.java | 5 +- packages/camera/camera/lib/camera.dart | 20 +- .../method_channel/method_channel_camera.dart | 69 +- .../test/method_channel_camera_test.dart | 22 +- 6 files changed, 534 insertions(+), 566 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 9cf111b9ee69..46365bd7c693 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -35,6 +35,7 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -47,501 +48,476 @@ import java.util.concurrent.Executors; public class Camera { - private final SurfaceTextureEntry flutterTexture; - private final CameraManager cameraManager; - private final OrientationEventListener orientationEventListener; - private final boolean isFrontFacing; - private final int sensorOrientation; - private final String cameraName; - private final Size captureSize; - private final Size previewSize; - private final boolean enableAudio; - - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private ImageReader pictureImageReader; - private ImageReader imageStreamReader; - private DartMessenger dartMessenger; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; - private boolean recordingVideo; - private CamcorderProfile recordingProfile; - private int currentOrientation = ORIENTATION_UNKNOWN; - - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, - } - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final String cameraName, - final String resolutionPreset, - final boolean enableAudio) - throws CameraAccessException { - if (activity == null) { - throw new IllegalStateException("No activity available!"); - } - this.cameraName = cameraName; - this.enableAudio = enableAudio; - this.flutterTexture = flutterTexture; - this.dartMessenger = dartMessenger; - this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); - orientationEventListener = - new OrientationEventListener(activity.getApplicationContext()) { - @Override - public void onOrientationChanged(int i) { - if (i == ORIENTATION_UNKNOWN) { - return; - } - // Convert the raw deg angle to the nearest multiple of 90. - currentOrientation = (int) Math.round(i / 90.0) * 90; - } - }; - orientationEventListener.enable(); - - CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - //noinspection ConstantConditions - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - //noinspection ConstantConditions - isFrontFacing = - characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); - recordingProfile = - CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); - captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); - previewSize = computeBestPreviewSize(cameraName, preset); - } - - private void prepareMediaRecorder(String outputFilePath) throws IOException { - if (mediaRecorder != null) { - mediaRecorder.release(); + private final SurfaceTextureEntry flutterTexture; + private final CameraManager cameraManager; + private final OrientationEventListener orientationEventListener; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final String cameraName; + private final Size captureSize; + private final Size previewSize; + private final boolean enableAudio; + + private CameraDevice cameraDevice; + private CameraCaptureSession cameraCaptureSession; + private ImageReader pictureImageReader; + private ImageReader imageStreamReader; + private DartMessenger dartMessenger; + private CaptureRequest.Builder captureRequestBuilder; + private MediaRecorder mediaRecorder; + private boolean recordingVideo; + private CamcorderProfile recordingProfile; + private int currentOrientation = ORIENTATION_UNKNOWN; + private Context applicationContext; + + // Mirrors camera.dart + public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, } - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); - } - - @SuppressLint("MissingPermission") - public void open(@NonNull final Result result) throws CameraAccessException { - pictureImageReader = - ImageReader.newInstance( - captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); - - // Used to steam image byte data to dart side. - imageStreamReader = - ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); - - cameraManager.openCamera( - cameraName, - new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice device) { - cameraDevice = device; - try { - startPreview(); - } catch (CameraAccessException e) { - result.error("CameraAccess", e.getMessage(), null); - close(); - return; - } - Map reply = new HashMap<>(); - reply.put("textureId", flutterTexture.id()); - reply.put("previewWidth", previewSize.getWidth()); - reply.put("previewHeight", previewSize.getHeight()); - result.success(reply); - } - - @Override - public void onClosed(@NonNull CameraDevice camera) { - dartMessenger.sendCameraClosingEvent(); - super.onClosed(camera); - } - - @Override - public void onDisconnected(@NonNull CameraDevice cameraDevice) { - close(); - dartMessenger.send(DartMessenger.EventType.ERROR, "The camera was disconnected."); - } - - @Override - public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { - close(); - String errorDescription; - switch (errorCode) { - case ERROR_CAMERA_IN_USE: - errorDescription = "The camera device is in use already."; - break; - case ERROR_MAX_CAMERAS_IN_USE: - errorDescription = "Max cameras in use"; - break; - case ERROR_CAMERA_DISABLED: - errorDescription = "The camera device could not be opened due to a device policy."; - break; - case ERROR_CAMERA_DEVICE: - errorDescription = "The camera device has encountered a fatal error"; - break; - case ERROR_CAMERA_SERVICE: - errorDescription = "The camera service has encountered a fatal error."; - break; - default: - errorDescription = "Unknown camera error"; - } - dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription); - } - }, - null); - } - - private void writeToFile(ByteBuffer buffer, File file) throws IOException { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - while (0 < buffer.remaining()) { - outputStream.getChannel().write(buffer); - } + public Camera( + final Activity activity, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, + final String cameraName, + final String resolutionPreset, + final boolean enableAudio) + throws CameraAccessException { + if (activity == null) { + throw new IllegalStateException("No activity available!"); + } + this.cameraName = cameraName; + this.enableAudio = enableAudio; + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; + this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + orientationEventListener = + new OrientationEventListener(activity.getApplicationContext()) { + @Override + public void onOrientationChanged(int i) { + if (i == ORIENTATION_UNKNOWN) { + return; + } + // Convert the raw deg angle to the nearest multiple of 90. + currentOrientation = (int) Math.round(i / 90.0) * 90; + } + }; + orientationEventListener.enable(); + + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + StreamConfigurationMap streamConfigurationMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + //noinspection ConstantConditions + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + //noinspection ConstantConditions + isFrontFacing = + characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = + CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + previewSize = computeBestPreviewSize(cameraName, preset); } - } - SurfaceTextureEntry getFlutterTexture() { - return flutterTexture; - } + private void prepareMediaRecorder(String outputFilePath) throws IOException { + if (mediaRecorder != null) { + mediaRecorder.release(); + } - public void takePicture(String filePath, @NonNull final Result result) { - final File file = new File(filePath); + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } - if (file.exists()) { - result.error( - "fileExists", "File at path '" + filePath + "' already exists. Cannot overwrite.", null); - return; + @SuppressLint("MissingPermission") + public void open(@NonNull final Result result) throws CameraAccessException { + pictureImageReader = + ImageReader.newInstance( + captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); + + // Used to steam image byte data to dart side. + imageStreamReader = + ImageReader.newInstance( + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + + cameraManager.openCamera( + cameraName, + new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice device) { + cameraDevice = device; + try { + startPreview(); + } catch (CameraAccessException e) { + result.error("CameraAccess", e.getMessage(), null); + close(); + return; + } + Map reply = new HashMap<>(); + reply.put("cameraId", flutterTexture.id()); + result.success(reply); + dartMessenger.sendResolutionChangedEvent(previewSize.getWidth(), previewSize.getHeight(), null, null); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + dartMessenger.sendCameraClosingEvent(); + super.onClosed(camera); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + close(); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { + close(); + String errorDescription; + switch (errorCode) { + case ERROR_CAMERA_IN_USE: + errorDescription = "The camera device is in use already."; + break; + case ERROR_MAX_CAMERAS_IN_USE: + errorDescription = "Max cameras in use"; + break; + case ERROR_CAMERA_DISABLED: + errorDescription = "The camera device could not be opened due to a device policy."; + break; + case ERROR_CAMERA_DEVICE: + errorDescription = "The camera device has encountered a fatal error"; + break; + case ERROR_CAMERA_SERVICE: + errorDescription = "The camera service has encountered a fatal error."; + break; + default: + errorDescription = "Unknown camera error"; + } + dartMessenger.sendCameraErrorEvent(errorDescription); + } + }, + null); } - pictureImageReader.setOnImageAvailableListener( - reader -> { - try (Image image = reader.acquireLatestImage()) { - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - writeToFile(buffer, file); - result.success(null); - } catch (IOException e) { - result.error("IOError", "Failed saving image", null); - } - }, - null); - - try { - final CaptureRequest.Builder captureBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureBuilder.addTarget(pictureImageReader.getSurface()); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); + private void writeToFile(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + while (0 < buffer.remaining()) { + outputStream.getChannel().write(buffer); } - }, - null); - } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); - } - } - - private void createCaptureSession(int templateType, Surface... surfaces) - throws CameraAccessException { - createCaptureSession(templateType, null, surfaces); - } - - private void createCaptureSession( - int templateType, Runnable onSuccessCallback, Surface... surfaces) - throws CameraAccessException { - // Close any existing capture session. - closeCaptureSession(); - - // Create a new capture builder. - captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); - - // Build Flutter surface to render to - SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); - surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface flutterSurface = new Surface(surfaceTexture); - captureRequestBuilder.addTarget(flutterSurface); - - List remainingSurfaces = Arrays.asList(surfaces); - if (templateType != CameraDevice.TEMPLATE_PREVIEW) { - // If it is not preview mode, add all surfaces as targets. - for (Surface surface : remainingSurfaces) { - captureRequestBuilder.addTarget(surface); - } + } } - // Prepare the callback - CameraCaptureSession.StateCallback callback = - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - try { - if (cameraDevice == null) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "The camera was closed during configuration."); - return; - } - cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - if (onSuccessCallback != null) { - onSuccessCallback.run(); - } - } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.send(DartMessenger.EventType.ERROR, e.getMessage()); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "Failed to configure camera session."); - } - }; - - // Start the session - if (VERSION.SDK_INT >= VERSION_CODES.P) { - // Collect all surfaces we want to render to. - List configs = new ArrayList<>(); - configs.add(new OutputConfiguration(flutterSurface)); - for (Surface surface : remainingSurfaces) { - configs.add(new OutputConfiguration(surface)); - } - createCaptureSessionWithSessionConfig(configs, callback); - } else { - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); - createCaptureSession(surfaceList, callback); + SurfaceTextureEntry getFlutterTexture() { + return flutterTexture; } - } - - @TargetApi(VERSION_CODES.P) - private void createCaptureSessionWithSessionConfig( - List outputConfigs, CameraCaptureSession.StateCallback callback) - throws CameraAccessException { - cameraDevice.createCaptureSession( - new SessionConfiguration( - SessionConfiguration.SESSION_REGULAR, - outputConfigs, - Executors.newSingleThreadExecutor(), - callback)); - } - - @TargetApi(VERSION_CODES.LOLLIPOP) - @SuppressWarnings("deprecation") - private void createCaptureSession( - List surfaces, CameraCaptureSession.StateCallback callback) - throws CameraAccessException { - cameraDevice.createCaptureSession(surfaces, callback, null); - } - - public void startVideoRecording(String filePath, Result result) { - if (new File(filePath).exists()) { - result.error("fileExists", "File at path '" + filePath + "' already exists.", null); - return; + + public void takePicture(@NonNull final Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; + } + + pictureImageReader.setOnImageAvailableListener( + reader -> { + try (Image image = reader.acquireLatestImage()) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + writeToFile(buffer, file); + result.success(file.getAbsolutePath()); + } catch (IOException e) { + result.error("IOError", "Failed saving image", null); + } + }, + null); + + try { + final CaptureRequest.Builder captureBuilder = + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(pictureImageReader.getSurface()); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + result.error("captureFailure", reason, null); + } + }, + null); + } catch (CameraAccessException e) { + result.error("cameraAccess", e.getMessage(), null); + } } - try { - prepareMediaRecorder(filePath); - recordingVideo = true; - createCaptureSession( - CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(null); - } catch (CameraAccessException | IOException e) { - result.error("videoRecordingFailed", e.getMessage(), null); + + private void createCaptureSession(int templateType, Surface... surfaces) + throws CameraAccessException { + createCaptureSession(templateType, null, surfaces); } - } - public void stopVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + private void createCaptureSession( + int templateType, Runnable onSuccessCallback, Surface... surfaces) + throws CameraAccessException { + // Close any existing capture session. + closeCaptureSession(); + + // Create a new capture builder. + captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); + + // Build Flutter surface to render to + SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface flutterSurface = new Surface(surfaceTexture); + captureRequestBuilder.addTarget(flutterSurface); + + List remainingSurfaces = Arrays.asList(surfaces); + if (templateType != CameraDevice.TEMPLATE_PREVIEW) { + // If it is not preview mode, add all surfaces as targets. + for (Surface surface : remainingSurfaces) { + captureRequestBuilder.addTarget(surface); + } + } + + // Prepare the callback + CameraCaptureSession.StateCallback callback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + if (cameraDevice == null) { + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); + return; + } + cameraCaptureSession = session; + captureRequestBuilder.set( + CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + if (onSuccessCallback != null) { + onSuccessCallback.run(); + } + } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); + } + }; + + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + // Start the session + cameraDevice.createCaptureSession(surfaceList, callback, null); } - try { - recordingVideo = false; - mediaRecorder.stop(); - mediaRecorder.reset(); - startPreview(); - result.success(null); - } catch (CameraAccessException | IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; + } + + try { + prepareMediaRecorder(file.getAbsolutePath()); + recordingVideo = true; + createCaptureSession( + CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + result.success(file.getAbsolutePath()); + } catch (CameraAccessException | IOException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } } - } - public void pauseVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + public void stopVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + recordingVideo = false; + mediaRecorder.stop(); + mediaRecorder.reset(); + startPreview(); + result.success(null); + } catch (CameraAccessException | IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } } - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + public void pauseVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.pause(); + } else { + result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } + + result.success(null); } - result.success(null); - } + public void resumeVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.resume(); + } else { + result.error( + "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } - public void resumeVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + result.success(null); } - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - } else { - result.error( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + public void startPreview() throws CameraAccessException { + if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; + + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); } - result.success(null); - } - - public void startPreview() throws CameraAccessException { - if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; - - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); - } - - public void startPreviewWithImageStream(EventChannel imageStreamChannel) - throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); - - imageStreamChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink imageStreamSink) { - setImageStreamImageAvailableListener(imageStreamSink); - } - - @Override - public void onCancel(Object o) { - imageStreamReader.setOnImageAvailableListener(null, null); - } - }); - } - - private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { - imageStreamReader.setOnImageAvailableListener( - reader -> { - Image img = reader.acquireLatestImage(); - if (img == null) return; - - List> planes = new ArrayList<>(); - for (Image.Plane plane : img.getPlanes()) { - ByteBuffer buffer = plane.getBuffer(); - - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes, 0, bytes.length); - - Map planeBuffer = new HashMap<>(); - planeBuffer.put("bytesPerRow", plane.getRowStride()); - planeBuffer.put("bytesPerPixel", plane.getPixelStride()); - planeBuffer.put("bytes", bytes); - - planes.add(planeBuffer); - } - - Map imageBuffer = new HashMap<>(); - imageBuffer.put("width", img.getWidth()); - imageBuffer.put("height", img.getHeight()); - imageBuffer.put("format", img.getFormat()); - imageBuffer.put("planes", planes); - - imageStreamSink.success(imageBuffer); - img.close(); - }, - null); - } - - private void closeCaptureSession() { - if (cameraCaptureSession != null) { - cameraCaptureSession.close(); - cameraCaptureSession = null; + public void startPreviewWithImageStream(EventChannel imageStreamChannel) + throws CameraAccessException { + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); + + imageStreamChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink imageStreamSink) { + setImageStreamImageAvailableListener(imageStreamSink); + } + + @Override + public void onCancel(Object o) { + imageStreamReader.setOnImageAvailableListener(null, null); + } + }); } - } - public void close() { - closeCaptureSession(); + private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { + imageStreamReader.setOnImageAvailableListener( + reader -> { + Image img = reader.acquireLatestImage(); + if (img == null) return; + + List> planes = new ArrayList<>(); + for (Image.Plane plane : img.getPlanes()) { + ByteBuffer buffer = plane.getBuffer(); + + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes, 0, bytes.length); + + Map planeBuffer = new HashMap<>(); + planeBuffer.put("bytesPerRow", plane.getRowStride()); + planeBuffer.put("bytesPerPixel", plane.getPixelStride()); + planeBuffer.put("bytes", bytes); + + planes.add(planeBuffer); + } + + Map imageBuffer = new HashMap<>(); + imageBuffer.put("width", img.getWidth()); + imageBuffer.put("height", img.getHeight()); + imageBuffer.put("format", img.getFormat()); + imageBuffer.put("planes", planes); + + imageStreamSink.success(imageBuffer); + img.close(); + }, + null); + } - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; + private void closeCaptureSession() { + if (cameraCaptureSession != null) { + cameraCaptureSession.close(); + cameraCaptureSession = null; + } } - if (pictureImageReader != null) { - pictureImageReader.close(); - pictureImageReader = null; + + public void close() { + closeCaptureSession(); + + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + } + if (pictureImageReader != null) { + pictureImageReader.close(); + pictureImageReader = null; + } + if (imageStreamReader != null) { + imageStreamReader.close(); + imageStreamReader = null; + } + if (mediaRecorder != null) { + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; + } } - if (imageStreamReader != null) { - imageStreamReader.close(); - imageStreamReader = null; + + public void dispose() { + close(); + flutterTexture.release(); + orientationEventListener.disable(); } - if (mediaRecorder != null) { - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; + + private int getMediaOrientation() { + final int sensorOrientationOffset = + (currentOrientation == ORIENTATION_UNKNOWN) + ? 0 + : (isFrontFacing) ? -currentOrientation : currentOrientation; + return (sensorOrientationOffset + sensorOrientation + 360) % 360; } - } - - public void dispose() { - close(); - flutterTexture.release(); - orientationEventListener.disable(); - } - - private int getMediaOrientation() { - final int sensorOrientationOffset = - (currentOrientation == ORIENTATION_UNKNOWN) - ? 0 - : (isFrontFacing) ? -currentOrientation : currentOrientation; - return (sensorOrientationOffset + sensorOrientation + 360) % 360; - } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 08b36cb2cb87..9f31fb77875c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -1,51 +1,57 @@ package io.flutter.plugins.camera; import android.text.TextUtils; + import androidx.annotation.Nullable; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; + import java.util.HashMap; import java.util.Map; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; + class DartMessenger { - @Nullable private EventChannel.EventSink eventSink; - - enum EventType { - ERROR, - CAMERA_CLOSING, - } - - DartMessenger(BinaryMessenger messenger, long eventChannelId) { - new EventChannel(messenger, "flutter.io/cameraPlugin/camera" + eventChannelId) - .setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object arguments, EventChannel.EventSink sink) { - eventSink = sink; - } - - @Override - public void onCancel(Object arguments) { - eventSink = null; - } - }); - } - - void sendCameraClosingEvent() { - send(EventType.CAMERA_CLOSING, null); - } - - void send(EventType eventType, @Nullable String description) { - if (eventSink == null) { - return; + @Nullable + private MethodChannel channel; + + + enum EventType { + ERROR, + CAMERA_CLOSING, + RESOLUTION_CHANGED, + } + + DartMessenger(BinaryMessenger messenger, long cameraId) { + channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); + } + + void sendResolutionChangedEvent(Integer previewWidth, Integer previewHeight, Integer captureWidth, Integer captureHeight) { + this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ + if (previewWidth != null) put("previewWidth", previewWidth); + if (previewHeight != null) put("previewHeight", previewHeight); + if (captureWidth != null) put("captureWidth", captureWidth); + if (captureHeight != null) put("captureHeight", captureHeight); + }}); + } + + void sendCameraClosingEvent() { + send(EventType.CAMERA_CLOSING); + } + + void sendCameraErrorEvent(@Nullable String description) { + this.send(EventType.ERROR, new HashMap() {{ + if (!TextUtils.isEmpty(description)) put("description", description); + }}); + } + + void send(EventType eventType) { + send(eventType, new HashMap<>()); } - Map event = new HashMap<>(); - event.put("eventType", eventType.toString().toLowerCase()); - // Only errors have a description. - if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { - event.put("description", description); + void send(EventType eventType, Map args) { + if (channel == null) { + return; + } + channel.invokeMethod(eventType.toString().toLowerCase(), args); } - eventSink.success(event); - } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 132075555f26..b6fcf89ac362 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -69,12 +69,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.error(errCode, errDesc, null); } }); - break; } case "takePicture": { - camera.takePicture(call.argument("path"), result); + camera.takePicture(result); break; } case "prepareForVideoRecording": @@ -85,7 +84,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } case "startVideoRecording": { - camera.startVideoRecording(call.argument("filePath"), result); + camera.startVideoRecording(result); break; } case "stopVideoRecording": diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 551efd65139a..e97ae19ea0df 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -190,7 +190,6 @@ class CameraController extends ValueNotifier { int _cameraId; bool _isDisposed = false; - StreamSubscription _eventSubscription; StreamSubscription _imageStreamSubscription; Completer _creatingCompleter; @@ -215,14 +214,22 @@ class CameraController extends ValueNotifier { description, resolutionPreset, enableAudio: enableAudio); - value = value.copyWith(isInitialized: true); + Size previewSize = await CameraPlatform.instance + .onResolutionChanged(_cameraId) + .take(1) + .map((event) => Size( + event.previewWidth.toDouble(), + event.previewHeight.toDouble(), + )) + .first; + + value = value.copyWith( + isInitialized: true, + previewSize: previewSize, + ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - _eventSubscription = - EventChannel('flutter.io/cameraPlugin/camera$_cameraId') - .receiveBroadcastStream() - .listen(_listener); _creatingCompleter.complete(); return _creatingCompleter.future; } @@ -491,7 +498,6 @@ class CameraController extends ValueNotifier { if (_creatingCompleter != null) { await _creatingCompleter.future; await CameraPlatform.instance.dispose(_cameraId); - await _eventSubscription?.cancel(); } } } diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 75a736fc14de..dbf16b8271f1 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -58,6 +58,7 @@ class MethodChannelCamera extends CameraPlatform { ResolutionPreset resolutionPreset, { bool enableAudio, }) async { + int _cameraId; try { final Map reply = await _channel.invokeMapMethod( @@ -70,35 +71,21 @@ class MethodChannelCamera extends CameraPlatform { 'enableAudio': enableAudio, }, ); - return reply['cameraId']; + _cameraId = reply['cameraId']; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - } - - @override - Future initializeCamera(int cameraId) { - _channels.putIfAbsent(cameraId, () { - final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); + if (!_channels.containsKey(_cameraId)) { + final channel = + MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); channel.setMethodCallHandler( - (MethodCall call) => handleMethodCall(call, cameraId)); - return channel; - }); - - Completer _completer = Completer(); - - onCameraInitialized(cameraId).first.then((value) { - _completer.complete(); - }); - - _channel.invokeMapMethod( - 'initialize', - { - 'cameraId': cameraId, - }, - ); - - return _completer.future; + (MethodCall call) => handleMethodCall(call, _cameraId)); + _channels[_cameraId] = channel; + _cameraResolutionChangedEventStreams[_cameraId] = _events(_cameraId) + .whereType() + .shareReplay(maxSize: 1); + } + return _cameraId; } @override @@ -165,17 +152,18 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) => + _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); @override Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); @override Future setFlashMode(int cameraId, FlashMode mode) => @@ -260,27 +248,20 @@ class MethodChannelCamera extends CameraPlatform { @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { - case 'initialized': - cameraEventStreamController.add(CameraInitializedEvent( + case 'resolution_changed': + _cameraEventStreamController.add(ResolutionChangedEvent( cameraId, call.arguments['previewWidth'], call.arguments['previewHeight'], )); break; - case 'resolution_changed': - cameraEventStreamController.add(CameraResolutionChangedEvent( - cameraId, - call.arguments['captureWidth'], - call.arguments['captureHeight'], - )); - break; case 'camera_closing': - cameraEventStreamController.add(CameraClosingEvent( + _cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'error': - cameraEventStreamController.add(CameraErrorEvent( + _cameraEventStreamController.add(CameraErrorEvent( cameraId, call.arguments['description'], )); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index f0ac45c239ec..e3cdd84fc432 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -24,7 +24,7 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1} + 'initialize': {'cameraId': 1} }); final camera = MethodChannelCamera(); @@ -53,8 +53,8 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, - 'dispose': {'textureId': 1} + 'initialize': {'cameraId': 1}, + 'dispose': {'cameraId': 1} }); final camera = MethodChannelCamera(); @@ -72,7 +72,7 @@ void main() { isNotNull, isMethodCall( 'dispose', - arguments: {'textureId': 1}, + arguments: {'cameraId': 1}, ), ]); }); @@ -85,7 +85,7 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, + 'initialize': {'cameraId': 1}, }, ); camera = MethodChannelCamera(); @@ -184,7 +184,7 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, + 'initialize': {'cameraId': 1}, }, ); camera = MethodChannelCamera(); @@ -237,7 +237,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); @@ -272,7 +272,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mkv'); @@ -291,7 +291,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); @@ -309,7 +309,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('pauseVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); @@ -327,7 +327,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('resumeVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); From a96f7859ce8dde68efc5dc5996546eb4da743c27 Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 19 Nov 2020 17:30:58 +0100 Subject: [PATCH 22/67] Fixed example app for Android. Removed isRecordingVideo and isStreamingImages from buildView method. --- packages/camera/camera/example/lib/main.dart | 5 ++++- packages/camera/camera/lib/camera.dart | 15 +-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index a23135f64f71..529bb9b8f97b 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -321,7 +321,10 @@ class _CameraExampleHomeState extends State void onVideoRecordButtonPressed() { startVideoRecording().then((XFile file) { if (mounted) setState(() {}); - if (file != null) showInSnackBar('Saving video to ${file.path}'); + if (file != null) { + showInSnackBar('Saving video to ${file.path}'); + videoFile = file; + } }); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index e97ae19ea0df..40943f6b9a81 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -320,22 +320,9 @@ class CameraController extends ValueNotifier { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', - 'startImageStream was called on uninitialized CameraController.', + 'buildView() was called on uninitialized CameraController.', ); } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ); - } - try { return CameraPlatform.instance.buildView(_cameraId); } on PlatformException catch (e) { From 28a366262cb8e21b5796612f3e376c49845abe4a Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Nov 2020 14:47:32 +0100 Subject: [PATCH 23/67] Added some first tests for camera/camera --- packages/camera/camera/pubspec.yaml | 1 + packages/camera/camera/test/camera_test.dart | 3 + .../camera/camera/test/camera_value_test.dart | 98 +++++++++++++++++++ .../test/method_channel_camera_test.dart | 20 ++-- 4 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 packages/camera/camera/test/camera_value_test.dart diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 705114d06f38..08f549bf241f 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -19,6 +19,7 @@ dev_dependencies: flutter_driver: sdk: flutter pedantic: ^1.8.0 + mockito: ^4.1.3 flutter: plugin: diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index cc33b369f000..d2521617a4ed 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:camera/camera.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; void main() { group('camera', () { @@ -35,6 +36,8 @@ void main() { }); } +class MockCameraPlatform extends Mock implements CameraPlatform {} + class MockCameraDescription extends CameraDescription { @override CameraLensDirection get lensDirection => CameraLensDirection.back; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart new file mode 100644 index 000000000000..34fb4ad13e88 --- /dev/null +++ b/packages/camera/camera/test/camera_value_test.dart @@ -0,0 +1,98 @@ +import 'dart:ui'; + +import 'package:camera/camera.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('camera_value', () { + test('Can be created', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + ); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isFalse); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, Size(10, 10)); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Can be created as uninitialized', () { + var cameraValue = const CameraValue.uninitialized(); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isFalse); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, null); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Can be copied with isInitialized', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith(isInitialized: true); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isTrue); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, null); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Has aspectRatio after setting size', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = + cv.copyWith(isInitialized: true, previewSize: Size(20, 10)); + + expect(cameraValue.aspectRatio, 0.5); + }); + + test('hasError is true after setting errorDescription', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith(errorDescription: 'error'); + + expect(cameraValue.hasError, isTrue); + expect(cameraValue.errorDescription, 'error'); + }); + + test('Recording paused is false when not recording', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith( + isInitialized: true, + isRecordingVideo: false, + isRecordingPaused: true); + + expect(cameraValue.isRecordingPaused, isFalse); + }); + + test('toString() works as expected', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + ); + + expect(cameraValue.toString(), + 'CameraValue(isRecordingVideo: false, isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); + }); + }); +} diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e3cdd84fc432..59d9bfb62e2e 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -107,16 +107,16 @@ void main() { final uhdEvent = ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert @@ -138,11 +138,11 @@ void main() { // Emit test events final event = CameraClosingEvent(cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); @@ -161,11 +161,11 @@ void main() { // Emit test events final event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); From 2ea1a00d998a904f4348d98882814efbb159147b Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 26 Nov 2020 14:34:24 +0100 Subject: [PATCH 24/67] More tests and some feedback --- packages/camera/camera/example/lib/main.dart | 4 +- packages/camera/camera/lib/camera.dart | 60 +------ packages/camera/camera/lib/camera_image.dart | 3 +- .../test}/camera_image_test.dart | 2 +- packages/camera/camera/test/camera_test.dart | 168 +++++++++++++++++- .../camera/camera/test/camera_value_test.dart | 2 +- .../method_channel/method_channel_camera.dart | 20 +-- .../lib/src/types/camera_image.dart | 148 --------------- .../test/method_channel_camera_test.dart | 12 +- 9 files changed, 190 insertions(+), 229 deletions(-) rename packages/camera/{camera_platform_interface/test/types => camera/test}/camera_image_test.dart (97%) delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_image.dart diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 529bb9b8f97b..aa99724528af 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -130,8 +130,8 @@ class _CameraExampleHomeState extends State ), ); } else { - return Container( - // aspectRatio: controller.value.aspectRatio, + return AspectRatio( + aspectRatio: controller.value.aspectRatio, child: controller.buildView(), ); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 40943f6b9a81..48d688788246 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -29,39 +29,6 @@ Future> availableCameras() async { return CameraPlatform.instance.availableCameras(); } -/// This is thrown when the plugin reports an error. -class CameraException implements Exception { - /// Creates a new camera exception with the given error code and description. - CameraException(this.code, this.description); - - /// Error code. - // TODO(bparrishMines): Document possible error codes. - // https://github.com/flutter/flutter/issues/69298 - String code; - - /// Textual description of the error. - String description; - - @override - String toString() => '$runtimeType($code, $description)'; -} - -/// A widget showing a live camera preview. -class CameraPreview extends StatelessWidget { - /// Creates a preview widget for the given camera controller. - const CameraPreview(this.controller); - - /// The controller for the camera that the preview is shown for. - final CameraController controller; - - @override - Widget build(BuildContext context) { - return controller.value.isInitialized - ? Texture(textureId: controller._cameraId) - : Container(); - } -} - /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. @@ -150,7 +117,6 @@ class CameraValue { @override String toString() { return '$runtimeType(' - 'isRecordingVideo: $isRecordingVideo, ' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' @@ -205,7 +171,10 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if the initialization fails. Future initialize() async { if (_isDisposed) { - return Future.value(); + throw CameraException( + 'Disposed CameraController', + 'initialize was called on a disposed CameraController', + ); } try { _creatingCompleter = Completer(); @@ -249,27 +218,6 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.prepareForVideoRecording(); } - /// Listen to events from the native plugins. - /// - /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. - void _listener(dynamic event) { - //TODO: Replace - debugPrint("event is $event"); - final Map map = event; - if (_isDisposed) { - return; - } - - switch (map['eventType']) { - case 'error': - value = value.copyWith(errorDescription: event['errorDescription']); - break; - case 'cameraClosing': - value = value.copyWith(isRecordingVideo: false); - break; - } - } - /// Captures an image and saves it to [path]. /// /// A path can for example be obtained using diff --git a/packages/camera/camera/lib/camera_image.dart b/packages/camera/camera/lib/camera_image.dart index cebc14873f52..0b84b19be6e8 100644 --- a/packages/camera/camera/lib/camera_image.dart +++ b/packages/camera/camera/lib/camera_image.dart @@ -113,7 +113,8 @@ ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { /// Although not all image formats are planar on iOS, we treat 1-dimensional /// images as single planar images. class CameraImage { - CameraImage._fromPlatformData(Map data) + /// CameraImage Constructor + CameraImage.fromPlatformData(Map data) : format = ImageFormat._fromPlatformData(data['format']), height = data['height'], width = data['width'], diff --git a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart b/packages/camera/camera/test/camera_image_test.dart similarity index 97% rename from packages/camera/camera_platform_interface/test/types/camera_image_test.dart rename to packages/camera/camera/test/camera_image_test.dart index 61ed75225cdf..c8f808f2c1a1 100644 --- a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart +++ b/packages/camera/camera/test/camera_image_test.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; -import 'package:camera_platform_interface/src/types/camera_image.dart'; +import 'package:camera/camera.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index d2521617a4ed..7b3db21f4b34 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1,11 +1,44 @@ // Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:ui'; + import 'package:camera/camera.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:rxdart/rxdart.dart'; + +get mockAvailableCameras => [ + CameraDescription( + name: 'camBack', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + CameraDescription( + name: 'camFront', + lensDirection: CameraLensDirection.front, + sensorOrientation: 180), + ]; + +get mockInitializeCamera => 13; + +get mockOnResolutionChangedEvent => + ResolutionChangedEvent(13, 100, 100, 75, 75); + +get mockOnCameraClosingEvent => null; + +get mockOnCameraErrorEvent => CameraErrorEvent(13, 'closing'); + +get mockTakePicture => null; + +get mockVideoRecordingXFile => null; void main() { + WidgetsFlutterBinding.ensureInitialized(); group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { @@ -33,10 +66,143 @@ void main() { throwsAssertionError, ); }); + + test('availableCameras() has camera', () async { + CameraPlatform.instance = MockCameraPlatform(); + + var camList = await availableCameras(); + + expect(camList, equals(mockAvailableCameras)); + }); + }); + + group('$CameraController', () { + setUpAll(() { + CameraPlatform.instance = MockCameraPlatform(); + }); + + test('Can be initialized', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + }); + + test('can be disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + + await cameraController.dispose(); + + verify(CameraPlatform.instance.dispose(13)).called(1); + }); + + test('initialize() returns when disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + + await cameraController.dispose(); + + verify(CameraPlatform.instance.dispose(13)).called(1); + expect(cameraController.value.isInitialized, isFalse); + + await cameraController.initialize(); + expect(cameraController.value.isInitialized, isTrue); + + + }); }); } -class MockCameraPlatform extends Mock implements CameraPlatform {} +class MockCameraPlatform extends Mock + with MockPlatformInterfaceMixin + implements CameraPlatform { + + @override + Future> availableCameras() => + Future.value(mockAvailableCameras); + + @override + Future initializeCamera( + CameraDescription cameraDescription, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) => + Future.value(mockInitializeCamera); + + @override + Stream onResolutionChanged(int cameraId) { + return Stream.value(mockOnResolutionChangedEvent); + } + + @override + Stream onCameraClosing(int cameraId) { + return Stream.value(mockOnCameraClosingEvent); + } + + @override + Stream onCameraError(int cameraId) { + return Stream.value(mockOnCameraErrorEvent); + } + + @override + Future takePicture(int cameraId) => Future.value(mockTakePicture); + + // @override + // Future prepareForVideoRecording() { + // + // } + + @override + Future startVideoRecording(int cameraId) => + Future.value(mockVideoRecordingXFile); + +// @override +// Future stopVideoRecording(int cameraId) { +// +// } + +// @override +// Future pauseVideoRecording(int cameraId) { +// throw UnimplementedError('pauseVideoRecording() is not implemented.'); +// } + +// @override +// Future resumeVideoRecording(int cameraId) { +// throw UnimplementedError('resumeVideoRecording() is not implemented.'); +// } + +// @override +// Widget buildView(int cameraId) { +// throw UnimplementedError('buildView() has not been implemented.'); +// } + +} class MockCameraDescription extends CameraDescription { @override diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 34fb4ad13e88..4a70f059cb5e 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -92,7 +92,7 @@ void main() { ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); }); }); } diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index dbf16b8271f1..86649683014f 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -76,8 +76,7 @@ class MethodChannelCamera extends CameraPlatform { throw CameraException(e.code, e.message); } if (!_channels.containsKey(_cameraId)) { - final channel = - MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); + final channel = MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleMethodCall(call, _cameraId)); _channels[_cameraId] = channel; @@ -152,18 +151,17 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future pauseVideoRecording(int cameraId) => - _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); @override Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); @override Future setFlashMode(int cameraId, FlashMode mode) => diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart deleted file mode 100644 index 5662f130d8a9..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; - -/// A single color plane of image data. -/// -/// The number and meaning of the planes in an image are determined by the -/// format of the Image. -class Plane { - Plane._fromPlatformData(Map data) - : bytes = data['bytes'], - bytesPerPixel = data['bytesPerPixel'], - bytesPerRow = data['bytesPerRow'], - height = data['height'], - width = data['width']; - - /// Bytes representing this plane. - final Uint8List bytes; - - /// The distance between adjacent pixel samples on Android, in bytes. - /// - /// Will be `null` on iOS. - final int bytesPerPixel; - - /// The row stride for this color plane, in bytes. - final int bytesPerRow; - - /// Height of the pixel buffer on iOS. - /// - /// Will be `null` on Android - final int height; - - /// Width of the pixel buffer on iOS. - /// - /// Will be `null` on Android. - final int width; -} - -// TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values. -/// Group of image formats that are comparable across Android and iOS platforms. -enum ImageFormatGroup { - /// The image format does not fit into any specific group. - unknown, - - /// Multi-plane YUV 420 format. - /// - /// This format is a generic YCbCr format, capable of describing any 4:2:0 - /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved), - /// with 8 bits per color sample. - /// - /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See - /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 - /// - /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See - /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc - yuv420, - - /// 32-bit BGRA. - /// - /// On iOS, this is `kCVPixelFormatType_32BGRA`. See - /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc - bgra8888, -} - -/// Describes how pixels are represented in an image. -class ImageFormat { - ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); - - /// Describes the format group the raw image format falls into. - final ImageFormatGroup group; - - /// Raw version of the format from the Android or iOS platform. - /// - /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See - /// https://developer.android.com/reference/android/graphics/ImageFormat - /// - /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. - /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc - final dynamic raw; -} - -ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { - if (defaultTargetPlatform == TargetPlatform.android) { - // android.graphics.ImageFormat.YUV_420_888 - if (rawFormat == 35) { - return ImageFormatGroup.yuv420; - } - } - - if (defaultTargetPlatform == TargetPlatform.iOS) { - switch (rawFormat) { - // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - case 875704438: - return ImageFormatGroup.yuv420; - // kCVPixelFormatType_32BGRA - case 1111970369: - return ImageFormatGroup.bgra8888; - } - } - - return ImageFormatGroup.unknown; -} - -/// A single complete image buffer from the platform camera. -/// -/// This class allows for direct application access to the pixel data of an -/// Image through one or more [Uint8List]. Each buffer is encapsulated in a -/// [Plane] that describes the layout of the pixel data in that plane. The -/// [CameraImage] is not directly usable as a UI resource. -/// -/// Although not all image formats are planar on iOS, we treat 1-dimensional -/// images as single planar images. -class CameraImage { - /// Creates a new [CameraImage] from the data received from the platform. - CameraImage.fromPlatformData(Map data) - : format = ImageFormat._fromPlatformData(data['format']), - height = data['height'], - width = data['width'], - planes = List.unmodifiable(data['planes'] - .map((dynamic planeData) => Plane._fromPlatformData(planeData))); - - /// Format of the image provided. - /// - /// Determines the number of planes needed to represent the image, and - /// the general layout of the pixel data in each [Uint8List]. - final ImageFormat format; - - /// Height of the image in pixels. - /// - /// For formats where some color channels are subsampled, this is the height - /// of the largest-resolution plane. - final int height; - - /// Width of the image in pixels. - /// - /// For formats where some color channels are subsampled, this is the width - /// of the largest-resolution plane. - final int width; - - /// The pixels planes for this image. - /// - /// The number of planes is determined by the format of the image. - final List planes; -} diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 59d9bfb62e2e..79237cdc1bb4 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -107,17 +107,13 @@ void main() { final uhdEvent = ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); From 484f1ceffa3033d562d9507336f18cf3726f7013 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 20 Nov 2020 15:42:59 +0100 Subject: [PATCH 25/67] iOS implementation: Removed standard event channel. Added reverse method channel. Updated initialize method. Added resolution changed event. Updated error reporting to use new method channel. --- .../camera/camera/ios/Classes/CameraPlugin.m | 99 ++++++------------- 1 file changed, 28 insertions(+), 71 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 525c1286717a..d9b38cd5a3bf 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -152,14 +152,13 @@ static ResolutionPreset getResolutionPresetForString(NSString *preset) { @interface FLTCam : NSObject + AVCaptureAudioDataOutputSampleBufferDelegate + > @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; -@property(nonatomic) FlutterEventChannel *eventChannel; @property(nonatomic) FLTImageStreamHandler *imageStreamHandler; -@property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) FlutterMethodChannel *methodChannel; @property(readonly, nonatomic) AVCaptureSession *captureSession; @property(readonly, nonatomic) AVCaptureDevice *captureDevice; @property(readonly, nonatomic) AVCapturePhotoOutput *capturePhotoOutput API_AVAILABLE(ios(10)); @@ -347,10 +346,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } } if (!CMSampleBufferDataIsReady(sampleBuffer)) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"sample buffer is not ready. Skipping sample" - }); + [_methodChannel invokeMethod:@"error" arguments:@"sample buffer is not ready. Skipping sample"]; return; } if (_isStreamingImages) { @@ -414,10 +410,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } if (_isRecording && !_isRecordingPaused) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; return; } @@ -500,20 +493,13 @@ - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_R - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_videoWriterInput.readyForMoreMediaData) { if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : - [NSString stringWithFormat:@"%@", @"Unable to write to video input"] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } } } @@ -521,20 +507,13 @@ - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_audioWriterInput.readyForMoreMediaData) { if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : - [NSString stringWithFormat:@"%@", @"Unable to write to audio input"] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } } } @@ -565,23 +544,10 @@ - (CVPixelBufferRef)copyPixelBuffer { return pixelBuffer; } -- (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - // need to unregister stream handler when disposing the camera - [_eventChannel setStreamHandler:nil]; - return nil; -} - -- (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments - eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; -} - - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result { if (!_isRecording) { if (![self setupWriterForPath:path]) { - _eventSink(@{@"event" : @"error", @"errorDescription" : @"Setup Writer Failed"}); + [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; return; } _isRecording = YES; @@ -592,7 +558,7 @@ - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result _audioIsDisconnected = NO; result(nil); } else { - _eventSink(@{@"event" : @"error", @"errorDescription" : @"Video is already recording!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; } } @@ -604,10 +570,7 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { result(nil); } else { - self->_eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"AVAssetWriter could not finish writing!" - }); + [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; } }]; } @@ -641,8 +604,7 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen _isStreamingImages = YES; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are already streaming!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; } } @@ -651,8 +613,7 @@ - (void)stopImageStream { _isStreamingImages = NO; _imageStreamHandler = nil; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are not streaming!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; } } @@ -672,7 +633,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { error:&error]; NSParameterAssert(_videoWriter); if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:@"error" arguments:error.description]; return NO; } NSDictionary *videoSettings = [NSDictionary @@ -726,7 +687,7 @@ - (void)setUpCaptureSessionForAudio { AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:@"error" arguments:error.description]; } // Setup the audio output. _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; @@ -738,10 +699,7 @@ - (void)setUpCaptureSessionForAudio { [_captureSession addOutput:_audioOutput]; _isAudioSetup = YES; } else { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"Unable to add Audio input/output to session capture" - }); + [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; _isAudioSetup = NO; } } @@ -841,20 +799,19 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re cam.onFrameAvailable = ^{ [weakSelf.registry textureFrameAvailable:textureId]; }; - FlutterEventChannel *eventChannel = [FlutterEventChannel - eventChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/cameraEvents%lld", - textureId] - binaryMessenger:_messenger]; - [eventChannel setStreamHandler:cam]; - cam.eventChannel = eventChannel; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + textureId] + binaryMessenger:_messenger]; + cam.methodChannel = methodChannel; result(@{ - @"textureId" : @(textureId), + @"cameraId" : @(textureId), + }); + [methodChannel invokeMethod:@"resolution_changed" arguments:@{ @"previewWidth" : @(cam.previewSize.width), - @"previewHeight" : @(cam.previewSize.height), - @"captureWidth" : @(cam.captureSize.width), - @"captureHeight" : @(cam.captureSize.height), - }); + @"previewHeight" : @(cam.previewSize.height) + }]; [cam start]; } } else if ([@"startImageStream" isEqualToString:call.method]) { From 85716650a5310666ac9aa8d5cf2c614366b201bb Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 26 Nov 2020 11:51:33 +0100 Subject: [PATCH 26/67] Started splitting initialize method --- .../camera/example/ios/Flutter/.last_build_id | 1 + .../example/ios/Flutter/Flutter.podspec | 18 + .../ios/Runner.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../camera/camera/ios/Classes/CameraPlugin.m | 1304 +++++++++-------- packages/camera/camera/lib/camera.dart | 2 +- .../method_channel/method_channel_camera.dart | 55 +- .../test/method_channel_camera_test.dart | 8 +- .../example/ios/Flutter/Flutter.podspec | 18 + .../ios/Runner.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + 11 files changed, 765 insertions(+), 701 deletions(-) create mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id create mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec create mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec create mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id new file mode 100644 index 000000000000..003e79479ba4 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +ae2fda886f38c43aa68f58016de359e7 \ No newline at end of file diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj index 862ee64fb666..d51240a02c14 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,7 +34,6 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 483D985F075B951ADBAD218E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -48,7 +41,6 @@ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -181,6 +169,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 7624MWN53C; }; }; }; @@ -229,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3E30118C54AB12C3EB9EDF27 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -269,9 +258,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -315,7 +307,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -372,7 +363,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -426,6 +416,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -447,6 +438,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index d9b38cd5a3bf..a722b65b1063 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -9,9 +9,9 @@ #import static FlutterError *getFlutterError(NSError *error) { - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.localizedDescription - details:error.domain]; + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] + message:error.localizedDescription + details:error.domain]; } @interface FLTSavePhotoDelegate : NSObject @@ -28,132 +28,132 @@ @interface FLTImageStreamHandler : NSObject @implementation FLTImageStreamHandler - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - return nil; + _eventSink = nil; + return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; + _eventSink = events; + return nil; } @end @implementation FLTSavePhotoDelegate { - /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. - FLTSavePhotoDelegate *selfReference; + /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. + FLTSavePhotoDelegate *selfReference; } - initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager - cameraPosition:(AVCaptureDevicePosition)cameraPosition { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _path = path; - _result = result; - _motionManager = motionManager; - _cameraPosition = cameraPosition; - selfReference = self; - return self; + result:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager +cameraPosition:(AVCaptureDevicePosition)cameraPosition { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _path = path; + _result = result; + _motionManager = motionManager; + _cameraPosition = cameraPosition; + selfReference = self; + return self; } - (void)captureOutput:(AVCapturePhotoOutput *)output - didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer - previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer - resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings - bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings - error:(NSError *)error API_AVAILABLE(ios(10)) { - selfReference = nil; - if (error) { - _result(getFlutterError(error)); - return; - } - NSData *data = [AVCapturePhotoOutput - JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer - previewPhotoSampleBuffer:previewPhotoSampleBuffer]; - UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage - scale:1.0 - orientation:[self getImageRotation]]; - // TODO(sigurdm): Consider writing file asynchronously. - bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; - if (!success) { - _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); - return; - } - _result(nil); +didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer +previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer + resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings + bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings + error:(NSError *)error API_AVAILABLE(ios(10)) { + selfReference = nil; + if (error) { + _result(getFlutterError(error)); + return; + } + NSData *data = [AVCapturePhotoOutput + JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer + previewPhotoSampleBuffer:previewPhotoSampleBuffer]; + UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage + scale:1.0 + orientation:[self getImageRotation]]; + // TODO(sigurdm): Consider writing file asynchronously. + bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; + if (!success) { + _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); + return; + } + _result(nil); } - (UIImageOrientation)getImageRotation { - float const threshold = 45.0; - BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { - return fabsf(value1 - value2) < threshold; - }; - BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { - return isNearValue(fabsf(value1), fabsf(value2)); - }; - float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, - _motionManager.accelerometerData.acceleration.x)) * - 180 / M_PI; - if (isNearValue(-90.0, yxAtan)) { - return UIImageOrientationRight; - } else if (isNearValueABS(180.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp - : UIImageOrientationDown; - } else if (isNearValueABS(0.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ - : UIImageOrientationUp /*do not rotate*/; - } else if (isNearValue(90.0, yxAtan)) { - return UIImageOrientationLeft; - } - // If none of the above, then the device is likely facing straight down or straight up -- just - // pick something arbitrary - // TODO: Maybe use the UIInterfaceOrientation if in these scenarios - return UIImageOrientationUp; + float const threshold = 45.0; + BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { + return fabsf(value1 - value2) < threshold; + }; + BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { + return isNearValue(fabsf(value1), fabsf(value2)); + }; + float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, + _motionManager.accelerometerData.acceleration.x)) * + 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { + return UIImageOrientationRight; + } else if (isNearValueABS(180.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp + : UIImageOrientationDown; + } else if (isNearValueABS(0.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ + : UIImageOrientationUp /*do not rotate*/; + } else if (isNearValue(90.0, yxAtan)) { + return UIImageOrientationLeft; + } + // If none of the above, then the device is likely facing straight down or straight up -- just + // pick something arbitrary + // TODO: Maybe use the UIInterfaceOrientation if in these scenarios + return UIImageOrientationUp; } @end // Mirrors ResolutionPreset in camera.dart typedef enum { - veryLow, - low, - medium, - high, - veryHigh, - ultraHigh, - max, + veryLow, + low, + medium, + high, + veryHigh, + ultraHigh, + max, } ResolutionPreset; static ResolutionPreset getResolutionPresetForString(NSString *preset) { - if ([preset isEqualToString:@"veryLow"]) { - return veryLow; - } else if ([preset isEqualToString:@"low"]) { - return low; - } else if ([preset isEqualToString:@"medium"]) { - return medium; - } else if ([preset isEqualToString:@"high"]) { - return high; - } else if ([preset isEqualToString:@"veryHigh"]) { - return veryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return ultraHigh; - } else if ([preset isEqualToString:@"max"]) { - return max; - } else { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"Unknown resolution preset %@", preset] - }]; - @throw error; - } + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } } @interface FLTCam : NSObject +AVCaptureVideoDataOutputSampleBufferDelegate, +AVCaptureAudioDataOutputSampleBufferDelegate +> @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; @@ -189,7 +189,7 @@ @interface FLTCam : NSObject 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + + CFRetain(sampleBuffer); + CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + + if (_videoWriter.status != AVAssetWriterStatusWriting) { + [_videoWriter startWriting]; + [_videoWriter startSessionAtSourceTime:currentSampleTime]; + } + + if (output == _captureVideoOutput) { + if (_videoIsDisconnected) { + _videoIsDisconnected = NO; + + if (_videoTimeOffset.value == 0) { + _videoTimeOffset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); + } else { + CMTime offset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); + _videoTimeOffset = CMTimeAdd(_videoTimeOffset, offset); + } + + return; + } + + _lastVideoSampleTime = currentSampleTime; + + CVPixelBufferRef nextBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CMTime nextSampleTime = CMTimeSubtract(_lastVideoSampleTime, _videoTimeOffset); + [_videoAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextSampleTime]; } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); + CMTime dur = CMSampleBufferGetDuration(sampleBuffer); + + if (dur.value > 0) { + currentSampleTime = CMTimeAdd(currentSampleTime, dur); + } + + if (_audioIsDisconnected) { + _audioIsDisconnected = NO; + + if (_audioTimeOffset.value == 0) { + _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + } else { + CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); + } + + return; + } + + _lastAudioSampleTime = currentSampleTime; + + if (_audioTimeOffset.value != 0) { + CFRelease(sampleBuffer); + sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; + } + + [self newAudioSample:sampleBuffer]; } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { + CFRelease(sampleBuffer); - sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; - } - - [self newAudioSample:sampleBuffer]; } - - CFRelease(sampleBuffer); - } } - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED { - CMItemCount count; - CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); - CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); - CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); - for (CMItemCount i = 0; i < count; i++) { - pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); - pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); - } - CMSampleBufferRef sout; - CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); - free(pInfo); - return sout; + CMItemCount count; + CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); + CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); + CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); + for (CMItemCount i = 0; i < count; i++) { + pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); + pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); + } + CMSampleBufferRef sout; + CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); + free(pInfo); + return sout; } - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + } + return; } - return; - } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; + if (_videoWriterInput.readyForMoreMediaData) { + if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; + } } - } } - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + } + return; } - return; - } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; + if (_audioWriterInput.readyForMoreMediaData) { + if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; + } } - } } - (void)close { - [_captureSession stopRunning]; - for (AVCaptureInput *input in [_captureSession inputs]) { - [_captureSession removeInput:input]; - } - for (AVCaptureOutput *output in [_captureSession outputs]) { - [_captureSession removeOutput:output]; - } + [_captureSession stopRunning]; + for (AVCaptureInput *input in [_captureSession inputs]) { + [_captureSession removeInput:input]; + } + for (AVCaptureOutput *output in [_captureSession outputs]) { + [_captureSession removeOutput:output]; + } } - (void)dealloc { - if (_latestPixelBuffer) { - CFRelease(_latestPixelBuffer); - } - [_motionManager stopAccelerometerUpdates]; + if (_latestPixelBuffer) { + CFRelease(_latestPixelBuffer); + } + [_motionManager stopAccelerometerUpdates]; } - (CVPixelBufferRef)copyPixelBuffer { - CVPixelBufferRef pixelBuffer = _latestPixelBuffer; - while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { - pixelBuffer = _latestPixelBuffer; - } - - return pixelBuffer; + CVPixelBufferRef pixelBuffer = _latestPixelBuffer; + while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { + pixelBuffer = _latestPixelBuffer; + } + + return pixelBuffer; } - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result { - if (!_isRecording) { - if (![self setupWriterForPath:path]) { - [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; - return; + if (!_isRecording) { + if (![self setupWriterForPath:path]) { + [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; + return; + } + _isRecording = YES; + _isRecordingPaused = NO; + _videoTimeOffset = CMTimeMake(0, 1); + _audioTimeOffset = CMTimeMake(0, 1); + _videoIsDisconnected = NO; + _audioIsDisconnected = NO; + result(nil); + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; } - _isRecording = YES; - _isRecordingPaused = NO; - _videoTimeOffset = CMTimeMake(0, 1); - _audioTimeOffset = CMTimeMake(0, 1); - _videoIsDisconnected = NO; - _audioIsDisconnected = NO; - result(nil); - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; - } } - (void)stopVideoRecordingWithResult:(FlutterResult)result { - if (_isRecording) { - _isRecording = NO; - if (_videoWriter.status != AVAssetWriterStatusUnknown) { - [_videoWriter finishWritingWithCompletionHandler:^{ - if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - result(nil); - } else { - [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + if (_isRecording) { + _isRecording = NO; + if (_videoWriter.status != AVAssetWriterStatusUnknown) { + [_videoWriter finishWritingWithCompletionHandler:^{ + if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { + result(nil); + } else { + [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + } + }]; } - }]; - } - } else { - NSError *error = + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(getFlutterError(error)); - } + result(getFlutterError(error)); + } } - (void)pauseVideoRecording { - _isRecordingPaused = YES; - _videoIsDisconnected = YES; - _audioIsDisconnected = YES; + _isRecordingPaused = YES; + _videoIsDisconnected = YES; + _audioIsDisconnected = YES; } - (void)resumeVideoRecording { - _isRecordingPaused = NO; + _isRecordingPaused = NO; } - (void)startImageStreamWithMessenger:(NSObject *)messenger { - if (!_isStreamingImages) { - FlutterEventChannel *eventChannel = + if (!_isStreamingImages) { + FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; - - _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; - - _isStreamingImages = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; - } + + _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; + [eventChannel setStreamHandler:_imageStreamHandler]; + + _isStreamingImages = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; + } } - (void)stopImageStream { - if (_isStreamingImages) { - _isStreamingImages = NO; - _imageStreamHandler = nil; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; - } + if (_isStreamingImages) { + _isStreamingImages = NO; + _imageStreamHandler = nil; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; + } } - (BOOL)setupWriterForPath:(NSString *)path { - NSError *error = nil; - NSURL *outputURL; - if (path != nil) { - outputURL = [NSURL fileURLWithPath:path]; - } else { - return NO; - } - if (_enableAudio && !_isAudioSetup) { - [self setUpCaptureSessionForAudio]; - } - _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL - fileType:AVFileTypeQuickTimeMovie - error:&error]; - NSParameterAssert(_videoWriter); - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - return NO; - } - NSDictionary *videoSettings = [NSDictionary - dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, + NSError *error = nil; + NSURL *outputURL; + if (path != nil) { + outputURL = [NSURL fileURLWithPath:path]; + } else { + return NO; + } + if (_enableAudio && !_isAudioSetup) { + [self setUpCaptureSessionForAudio]; + } + _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL + fileType:AVFileTypeQuickTimeMovie + error:&error]; + NSParameterAssert(_videoWriter); + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + return NO; + } + NSDictionary *videoSettings = [NSDictionary + dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey, [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey, nil]; - _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:videoSettings]; - - _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor - assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput - sourcePixelBufferAttributes:@{ - (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) - }]; - - NSParameterAssert(_videoWriterInput); - _videoWriterInput.expectsMediaDataInRealTime = YES; - - // Add the audio input - if (_enableAudio) { - AudioChannelLayout acl; - bzero(&acl, sizeof(acl)); - acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - NSDictionary *audioOutputSettings = nil; - // Both type of audio inputs causes output video file to be corrupted. - audioOutputSettings = [NSDictionary - dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, - [NSNumber numberWithFloat:44100.0], AVSampleRateKey, - [NSNumber numberWithInt:1], AVNumberOfChannelsKey, - [NSData dataWithBytes:&acl length:sizeof(acl)], - AVChannelLayoutKey, nil]; - _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:audioOutputSettings]; - _audioWriterInput.expectsMediaDataInRealTime = YES; - - [_videoWriter addInput:_audioWriterInput]; - [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - } - - [_videoWriter addInput:_videoWriterInput]; - [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - - return YES; + _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoSettings]; + + _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor + assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput + sourcePixelBufferAttributes:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) + }]; + + NSParameterAssert(_videoWriterInput); + _videoWriterInput.expectsMediaDataInRealTime = YES; + + // Add the audio input + if (_enableAudio) { + AudioChannelLayout acl; + bzero(&acl, sizeof(acl)); + acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + NSDictionary *audioOutputSettings = nil; + // Both type of audio inputs causes output video file to be corrupted. + audioOutputSettings = [NSDictionary + dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, + [NSNumber numberWithFloat:44100.0], AVSampleRateKey, + [NSNumber numberWithInt:1], AVNumberOfChannelsKey, + [NSData dataWithBytes:&acl length:sizeof(acl)], + AVChannelLayoutKey, nil]; + _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio + outputSettings:audioOutputSettings]; + _audioWriterInput.expectsMediaDataInRealTime = YES; + + [_videoWriter addInput:_audioWriterInput]; + [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + } + + [_videoWriter addInput:_videoWriterInput]; + [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + + return YES; } - (void)setUpCaptureSessionForAudio { - NSError *error = nil; - // Create a device input with the device and add it to the session. - // Setup the audio input. - AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice - error:&error]; - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - } - // Setup the audio output. - _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; - - if ([_captureSession canAddInput:audioInput]) { - [_captureSession addInput:audioInput]; - - if ([_captureSession canAddOutput:_audioOutput]) { - [_captureSession addOutput:_audioOutput]; - _isAudioSetup = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; - _isAudioSetup = NO; + NSError *error = nil; + // Create a device input with the device and add it to the session. + // Setup the audio input. + AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice + error:&error]; + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + } + // Setup the audio output. + _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; + + if ([_captureSession canAddInput:audioInput]) { + [_captureSession addInput:audioInput]; + + if ([_captureSession canAddOutput:_audioOutput]) { + [_captureSession addOutput:_audioOutput]; + _isAudioSetup = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; + _isAudioSetup = NO; + } } - } } @end @@ -713,144 +713,152 @@ @interface CameraPlugin () @end @implementation CameraPlugin { - dispatch_queue_t _dispatchQueue; + dispatch_queue_t _dispatchQueue; } + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" - binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" + binaryMessenger:[registrar messenger]]; + CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _registry = registry; - _messenger = messenger; - return self; + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = registry; + _messenger = messenger; + return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if (_dispatchQueue == nil) { - _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); - } - - // Invoke the plugin on another dispatch queue to avoid blocking the UI. - dispatch_async(_dispatchQueue, ^{ - [self handleMethodCallAsync:call result:result]; - }); + if (_dispatchQueue == nil) { + _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); + } + + // Invoke the plugin on another dispatch queue to avoid blocking the UI. + dispatch_async(_dispatchQueue, ^{ + [self handleMethodCallAsync:call result:result]; + }); } - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"availableCameras" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession - discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] - mediaType:AVMediaTypeVideo - position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = discoverySession.devices; - NSMutableArray *> *reply = - [[NSMutableArray alloc] initWithCapacity:devices.count]; - for (AVCaptureDevice *device in devices) { - NSString *lensFacing; - switch ([device position]) { - case AVCaptureDevicePositionBack: - lensFacing = @"back"; - break; - case AVCaptureDevicePositionFront: - lensFacing = @"front"; - break; - case AVCaptureDevicePositionUnspecified: - lensFacing = @"external"; - break; + if ([@"availableCameras" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + NSArray *devices = discoverySession.devices; + NSMutableArray *> *reply = + [[NSMutableArray alloc] initWithCapacity:devices.count]; + for (AVCaptureDevice *device in devices) { + NSString *lensFacing; + switch ([device position]) { + case AVCaptureDevicePositionBack: + lensFacing = @"back"; + break; + case AVCaptureDevicePositionFront: + lensFacing = @"front"; + break; + case AVCaptureDevicePositionUnspecified: + lensFacing = @"external"; + break; + } + [reply addObject:@{ + @"name" : [device uniqueID], + @"lensFacing" : lensFacing, + @"sensorOrientation" : @90, + }]; + } + result(reply); + } else { + result(FlutterMethodNotImplemented); } - [reply addObject:@{ - @"name" : [device uniqueID], - @"lensFacing" : lensFacing, - @"sensorOrientation" : @90, + } else if ([@"create" isEqualToString:call.method]) { + NSString *cameraName = call.arguments[@"cameraName"]; + NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; + NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName + resolutionPreset:resolutionPreset + enableAudio:[enableAudio boolValue] + dispatchQueue:_dispatchQueue + error:&error]; + + if (error) { + result(getFlutterError(error)); + } else { + if (_camera) { + [_camera close]; + } + int64_t textureId = [_registry registerTexture:cam]; + _camera = cam; + + result(@{ + @"cameraId" : @(textureId), + }); + } + } else if ([@"initialize" isEqualToString:call.method]) { + NSNumber* cameraIdArg = call.arguments[@"cameraId"]; + int64_t cameraId = [cameraIdArg longLongValue]; + + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) }]; - } - result(reply); - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"initialize" isEqualToString:call.method]) { - NSString *cameraName = call.arguments[@"cameraName"]; - NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - enableAudio:[enableAudio boolValue] - dispatchQueue:_dispatchQueue - error:&error]; - if (error) { - result(getFlutterError(error)); + [_camera start]; + result(nil); + } else if ([@"startImageStream" isEqualToString:call.method]) { + [_camera startImageStreamWithMessenger:_messenger]; + result(nil); + } else if ([@"stopImageStream" isEqualToString:call.method]) { + [_camera stopImageStream]; + result(nil); + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecording]; + result(nil); + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecording]; + result(nil); } else { - if (_camera) { - [_camera close]; - } - int64_t textureId = [_registry registerTexture:cam]; - _camera = cam; - __weak CameraPlugin *weakSelf = self; - cam.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:textureId]; - }; - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - textureId] - binaryMessenger:_messenger]; - cam.methodChannel = methodChannel; - result(@{ - @"cameraId" : @(textureId), - }); - [methodChannel invokeMethod:@"resolution_changed" arguments:@{ - @"previewWidth" : @(cam.previewSize.width), - @"previewHeight" : @(cam.previewSize.height) - }]; - [cam start]; - } - } else if ([@"startImageStream" isEqualToString:call.method]) { - [_camera startImageStreamWithMessenger:_messenger]; - result(nil); - } else if ([@"stopImageStream" isEqualToString:call.method]) { - [_camera stopImageStream]; - result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecording]; - result(nil); - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecording]; - result(nil); - } else { - NSDictionary *argsMap = call.arguments; - NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; - if ([@"takePicture" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - [_camera captureToFile:call.arguments[@"path"] result:result]; - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:textureId]; - [_camera close]; - _dispatchQueue = nil; - result(nil); - } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { - [_camera setUpCaptureSessionForAudio]; - result(nil); - } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; - } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; - } else { - result(FlutterMethodNotImplemented); + NSDictionary *argsMap = call.arguments; + NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; + if ([@"takePicture" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + [_camera captureToFile:call.arguments[@"path"] result:result]; + } else { + result(FlutterMethodNotImplemented); + } + } else if ([@"dispose" isEqualToString:call.method]) { + [_registry unregisterTexture:textureId]; + [_camera close]; + _dispatchQueue = nil; + result(nil); + } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { + [_camera setUpCaptureSessionForAudio]; + result(nil); + } else if ([@"startVideoRecording" isEqualToString:call.method]) { + [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; + } else if ([@"stopVideoRecording" isEqualToString:call.method]) { + [_camera stopVideoRecordingWithResult:result]; + } else { + result(FlutterMethodNotImplemented); + } } - } } @end diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 48d688788246..eaa615aa6be1 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -184,7 +184,7 @@ class CameraController extends ValueNotifier { enableAudio: enableAudio); Size previewSize = await CameraPlatform.instance - .onResolutionChanged(_cameraId) + .onCameraResolutionChanged(_cameraId) .take(1) .map((event) => Size( event.previewWidth.toDouble(), diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 86649683014f..bc836b0ac98e 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -10,7 +10,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:rxdart/rxdart.dart'; +import 'package:stream_transform/stream_transform.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); @@ -29,8 +29,6 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); - final Map _cameraResolutionChangedEventStreams = {}; - Stream _events(int cameraId) => cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); @@ -58,7 +56,6 @@ class MethodChannelCamera extends CameraPlatform { ResolutionPreset resolutionPreset, { bool enableAudio, }) async { - int _cameraId; try { final Map reply = await _channel.invokeMapMethod( @@ -71,20 +68,35 @@ class MethodChannelCamera extends CameraPlatform { 'enableAudio': enableAudio, }, ); - _cameraId = reply['cameraId']; + return reply['cameraId']; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - if (!_channels.containsKey(_cameraId)) { - final channel = MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); + } + + @override + Future initializeCamera(int cameraId) { + _channels.putIfAbsent(cameraId, () { + final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); channel.setMethodCallHandler( - (MethodCall call) => handleMethodCall(call, _cameraId)); - _channels[_cameraId] = channel; - _cameraResolutionChangedEventStreams[_cameraId] = _events(_cameraId) - .whereType() - .shareReplay(maxSize: 1); - } - return _cameraId; + (MethodCall call) => handleMethodCall(call, cameraId)); + return channel; + }); + + Completer _completer = Completer(); + + onCameraInitialized(cameraId).first.then((value) { + _completer.complete(); + }); + + _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': cameraId, + }, + ); + + return _completer.future; } @override @@ -246,20 +258,27 @@ class MethodChannelCamera extends CameraPlatform { @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { - case 'resolution_changed': - _cameraEventStreamController.add(ResolutionChangedEvent( + case 'initialized': + cameraEventStreamController.add(CameraInitializedEvent( cameraId, call.arguments['previewWidth'], call.arguments['previewHeight'], )); break; + case 'resolution_changed': + cameraEventStreamController.add(CameraResolutionChangedEvent( + cameraId, + call.arguments['captureWidth'], + call.arguments['captureHeight'], + )); + break; case 'camera_closing': - _cameraEventStreamController.add(CameraClosingEvent( + cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'error': - _cameraEventStreamController.add(CameraErrorEvent( + cameraEventStreamController.add(CameraErrorEvent( cameraId, call.arguments['description'], )); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 79237cdc1bb4..74dc0b00f181 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -97,15 +97,15 @@ void main() { test('Should receive resolution changes', () async { // Act - final Stream resolutionStream = - camera.onResolutionChanged(cameraId); + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); final streamQueue = StreamQueue(resolutionStream); // Emit test events final fhdEvent = - ResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); + CameraResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); final uhdEvent = - ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + CameraResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj index f6a2d6ec291a..75392aeb82e5 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,14 +34,12 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -230,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -270,9 +258,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -285,9 +276,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -331,7 +325,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -388,7 +381,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From f83d6b5c8e9465f3622a2602f7a3cce527472444 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 14:45:00 +0100 Subject: [PATCH 27/67] Finish splitting up initialize for iOS --- .../camera/example/ios/Flutter/.last_build_id | 2 +- .../camera/camera/ios/Classes/CameraPlugin.m | 46 +++++++++---------- packages/camera/camera/lib/camera.dart | 34 ++++++++------ 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id index 003e79479ba4..1d0b0dc32be3 100644 --- a/packages/camera/camera/example/ios/Flutter/.last_build_id +++ b/packages/camera/camera/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -ae2fda886f38c43aa68f58016de359e7 \ No newline at end of file +5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index a722b65b1063..7adb4558ed74 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -801,28 +801,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re @"cameraId" : @(textureId), }); } - } else if ([@"initialize" isEqualToString:call.method]) { - NSNumber* cameraIdArg = call.arguments[@"cameraId"]; - int64_t cameraId = [cameraIdArg longLongValue]; - - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:cameraId]; - }; - - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - cameraId] - binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel invokeMethod:@"initialized" arguments:@{ - @"previewWidth" : @(_camera.previewSize.width), - @"previewHeight" : @(_camera.previewSize.height) - }]; - [_camera start]; - result(nil); - } else if ([@"startImageStream" isEqualToString:call.method]) { + } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; result(nil); } else if ([@"stopImageStream" isEqualToString:call.method]) { @@ -836,15 +815,32 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(nil); } else { NSDictionary *argsMap = call.arguments; - NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; - if ([@"takePicture" isEqualToString:call.method]) { + NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; + if ([@"initialize" isEqualToString:call.method]) { + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + (long long) cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) + }]; + [_camera start]; + result(nil); + } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { [_camera captureToFile:call.arguments[@"path"] result:result]; } else { result(FlutterMethodNotImplemented); } } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:textureId]; + [_registry unregisterTexture:cameraId]; [_camera close]; _dispatchQueue = nil; result(nil); diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index eaa615aa6be1..80862495888f 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -9,7 +9,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; - +import 'package:pedantic/pedantic.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; part 'camera_image.dart'; @@ -178,23 +178,31 @@ class CameraController extends ValueNotifier { } try { _creatingCompleter = Completer(); + Completer _resolutionCompleter = Completer(); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); - _cameraId = await CameraPlatform.instance.initializeCamera( - description, resolutionPreset, - enableAudio: enableAudio); + unawaited( + CameraPlatform.instance + .onCameraInitialized(_cameraId) + .map((event) => Size( + event.previewWidth, + event.previewHeight, + )) + + .first + .then((previewSize) => _resolutionCompleter.complete(previewSize)), + ); - Size previewSize = await CameraPlatform.instance - .onCameraResolutionChanged(_cameraId) - .take(1) - .map((event) => Size( - event.previewWidth.toDouble(), - event.previewHeight.toDouble(), - )) - .first; + await CameraPlatform.instance.initializeCamera(_cameraId); value = value.copyWith( isInitialized: true, - previewSize: previewSize, + previewSize: await _resolutionCompleter.future, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); From 99b36eb3b4d38f097f1cfbee9721e0703ed9bcb7 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 15:29:37 +0100 Subject: [PATCH 28/67] Update unit tests --- .../test/method_channel_camera_test.dart | 104 ++++++++++++++---- 1 file changed, 84 insertions(+), 20 deletions(-) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 74dc0b00f181..4f9e65e67f82 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,27 +17,26 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelCamera', () { - group('Initialization & Disposal Tests', () { - test('Should receive a camera id when initialized', () async { + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { // Arrange MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1} + 'create': {'cameraId': 1} }); final camera = MethodChannelCamera(); // Act - final cameraId = await camera.initializeCamera( + final cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); // Assert - expect(cameraId, 1); expect(cameraMockChannel.log, [ isMethodCall( - 'initialize', + 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', @@ -46,6 +44,40 @@ void main() { }, ), ]); + expect(cameraId, 1); + }); + + test('Should send initialization data', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }); + final camera = MethodChannelCamera(); + final cameraId = await camera.createCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + + // Act + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + }, + ), + ]); }); test('Should send a disposal call on dispose', () async { @@ -53,15 +85,20 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null, 'dispose': {'cameraId': 1} }); final camera = MethodChannelCamera(); - final cameraId = await camera.initializeCamera( + final cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; // Act await camera.dispose(cameraId); @@ -69,7 +106,8 @@ void main() { // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ - isNotNull, + anything, + anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, @@ -85,14 +123,37 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null }, ); camera = MethodChannelCamera(); - cameraId = await camera.initializeCamera( + cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; + }); + + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final streamQueue = StreamQueue(eventStream); + + // Emit test events + final event = CameraInitializedEvent(cameraId, 3840, 2160); + await camera.handleMethodCall( + MethodCall('initialized', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); }); test('Should receive resolution changes', () async { @@ -102,10 +163,8 @@ void main() { final streamQueue = StreamQueue(resolutionStream); // Emit test events - final fhdEvent = - CameraResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); - final uhdEvent = - CameraResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + final fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); + final uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); await camera.handleMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( @@ -120,8 +179,8 @@ void main() { expect(await streamQueue.next, uhdEvent); expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); - // - // // Clean up + + // Clean up await streamQueue.cancel(); }); @@ -180,14 +239,19 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null }, ); camera = MethodChannelCamera(); - cameraId = await camera.initializeCamera( + cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; }); test('Should fetch CameraDescription instances for available cameras', From 53ad8a15b4f9fa945e0f97867bca24ae7bf115c1 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 17:46:01 +0100 Subject: [PATCH 29/67] Fix takePicture method on iOS --- .../camera/camera/ios/Classes/CameraPlugin.m | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 7adb4558ed74..ebfe7626f728 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -7,6 +7,7 @@ #import #import #import +#import static FlutterError *getFlutterError(NSError *error) { return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] @@ -44,20 +45,39 @@ @implementation FLTSavePhotoDelegate { FLTSavePhotoDelegate *selfReference; } -- initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager -cameraPosition:(AVCaptureDevicePosition)cameraPosition { +- initForResult:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager + cameraPosition:(AVCaptureDevicePosition)cameraPosition { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _path = path; _result = result; _motionManager = motionManager; _cameraPosition = cameraPosition; selfReference = self; + _path = [self getTemporaryFilePathWithExtension:@"jpg" prefix:@"CAP"]; return self; } +- (NSString*)getTemporaryFilePathWithExtension:(NSString*) extension prefix:(NSString*) prefix +{ + NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + NSString *fileDir = [[docDir stringByAppendingPathComponent:@"camera"] stringByAppendingPathComponent:@"pictures"]; + NSString *fileName = [prefix stringByAppendingString:[[NSUUID UUID] UUIDString]]; + NSString *file = [[fileDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:extension]; + + NSFileManager *fm = [NSFileManager defaultManager]; + if(![fm fileExistsAtPath:fileDir]) { + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:fileDir withIntermediateDirectories:true attributes:nil error:&error]; + if (error) { + _result(getFlutterError(error)); + return nil; + } + } + + return file; +} + - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer @@ -81,7 +101,7 @@ - (void)captureOutput:(AVCapturePhotoOutput *)output _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); return; } - _result(nil); + _result(_path); } - (UIImageOrientation)getImageRotation { @@ -256,17 +276,17 @@ - (void)stop { [_captureSession stopRunning]; } -- (void)captureToFile:(NSString *)path result:(FlutterResult)result API_AVAILABLE(ios(10)) { +- (void)captureToFile:(FlutterResult)result API_AVAILABLE(ios(10)) { AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; if (_resolutionPreset == max) { [settings setHighResolutionPhotoEnabled:YES]; } [_capturePhotoOutput capturePhotoWithSettings:settings - delegate:[[FLTSavePhotoDelegate alloc] initWithPath:path - result:result + delegate:[[FLTSavePhotoDelegate alloc] initForResult:result motionManager:_motionManager - cameraPosition:_captureDevice.position]]; + cameraPosition:_captureDevice.position + ]]; } - (void)setCaptureSessionPreset:(ResolutionPreset)resolutionPreset { @@ -835,7 +855,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(nil); } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { - [_camera captureToFile:call.arguments[@"path"] result:result]; + [_camera captureToFile:result]; } else { result(FlutterMethodNotImplemented); } From eadc3989cf9925dbcd39da9e4d188bd7fe676a92 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:04:47 +0100 Subject: [PATCH 30/67] Split initialize method on Android --- .../io/flutter/plugins/camera/Camera.java | 12 ++++----- .../flutter/plugins/camera/DartMessenger.java | 4 +-- .../plugins/camera/MethodCallHandlerImpl.java | 25 +++++++++++++++++-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 46365bd7c693..a6479953450a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -138,7 +138,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { } @SuppressLint("MissingPermission") - public void open(@NonNull final Result result) throws CameraAccessException { + public void open() throws CameraAccessException { pictureImageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); @@ -157,14 +157,14 @@ public void onOpened(@NonNull CameraDevice device) { try { startPreview(); } catch (CameraAccessException e) { - result.error("CameraAccess", e.getMessage(), null); + dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); return; } - Map reply = new HashMap<>(); - reply.put("cameraId", flutterTexture.id()); - result.success(reply); - dartMessenger.sendResolutionChangedEvent(previewSize.getWidth(), previewSize.getHeight(), null, null); + + dartMessenger.sendInitializedEvent( + previewSize.getWidth(), + previewSize.getHeight()); } @Override diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 9f31fb77875c..bbe4f374ecf6 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -25,12 +25,10 @@ enum EventType { channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); } - void sendResolutionChangedEvent(Integer previewWidth, Integer previewHeight, Integer captureWidth, Integer captureHeight) { + void sendInitializedEvent(Integer previewWidth, Integer previewHeight) { this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ if (previewWidth != null) put("previewWidth", previewWidth); if (previewHeight != null) put("previewHeight", previewHeight); - if (captureWidth != null) put("captureWidth", captureWidth); - if (captureHeight != null) put("captureHeight", captureHeight); }}); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index b6fcf89ac362..c2125a475dca 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -11,6 +11,8 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.view.TextureRegistry; +import java.util.HashMap; +import java.util.Map; final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final Activity activity; @@ -49,11 +51,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) handleException(e, result); } break; - case "initialize": + case "create": { if (camera != null) { camera.close(); } + cameraPermissions.requestPermissions( activity, permissionsRegistry, @@ -71,6 +74,22 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) }); break; } + case "initialize": { + if (camera != null) { + try { + camera.open(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error( + "cameraNotFound", + "Camera not found. Please call the 'create' method before calling 'initialize'.", + null); + } + break; + } case "takePicture": { camera.takePicture(result); @@ -156,7 +175,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce resolutionPreset, enableAudio); - camera.open(result); + Map reply = new HashMap<>(); + reply.put("cameraId", flutterSurfaceTexture.id()); + result.success(reply); } // We move catching CameraAccessException out of onMethodCall because it causes a crash From 752e764d6073c098bae9a8577ad2b6f4675bc221 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 12:52:05 +0100 Subject: [PATCH 31/67] Fix video recording on iOS. Updated platform interface. --- packages/camera/camera/example/lib/main.dart | 32 ++++----- .../camera/camera/ios/Classes/CameraPlugin.m | 70 +++++++++++-------- packages/camera/camera/lib/camera.dart | 24 +++---- .../platform_interface/camera_platform.dart | 8 +-- 4 files changed, 68 insertions(+), 66 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index aa99724528af..aa0c05d5eb33 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; class CameraExampleHome extends StatefulWidget { @@ -319,19 +318,19 @@ class _CameraExampleHomeState extends State } void onVideoRecordButtonPressed() { - startVideoRecording().then((XFile file) { + startVideoRecording().then((_) { if (mounted) setState(() {}); - if (file != null) { - showInSnackBar('Saving video to ${file.path}'); - videoFile = file; - } }); } void onStopButtonPressed() { - stopVideoRecording().then((_) { + stopVideoRecording().then((file) { if (mounted) setState(() {}); - showInSnackBar('Video recorded to: ${videoFile.path}'); + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } }); } @@ -349,39 +348,36 @@ class _CameraExampleHomeState extends State }); } - Future startVideoRecording() async { + Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); - return null; + return; } if (controller.value.isRecordingVideo) { // A recording is already started, do nothing. - return null; + return; } try { - XFile file = await controller.startVideoRecording(); - return file; + await controller.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); - return null; + return; } } - Future stopVideoRecording() async { + Future stopVideoRecording() async { if (!controller.value.isRecordingVideo) { return null; } try { - await controller.stopVideoRecording(); + return controller.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } - - await _startVideoPlayer(); } Future pauseVideoRecording() async { diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index ebfe7626f728..862e2b613f24 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -45,39 +45,20 @@ @implementation FLTSavePhotoDelegate { FLTSavePhotoDelegate *selfReference; } -- initForResult:(FlutterResult)result +- initWithPath:(NSString *)path + result:(FlutterResult)result motionManager:(CMMotionManager *)motionManager cameraPosition:(AVCaptureDevicePosition)cameraPosition { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _result = result; + _path = path; _motionManager = motionManager; _cameraPosition = cameraPosition; selfReference = self; - _path = [self getTemporaryFilePathWithExtension:@"jpg" prefix:@"CAP"]; + _result = result; return self; } -- (NSString*)getTemporaryFilePathWithExtension:(NSString*) extension prefix:(NSString*) prefix -{ - NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; - NSString *fileDir = [[docDir stringByAppendingPathComponent:@"camera"] stringByAppendingPathComponent:@"pictures"]; - NSString *fileName = [prefix stringByAppendingString:[[NSUUID UUID] UUIDString]]; - NSString *file = [[fileDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:extension]; - - NSFileManager *fm = [NSFileManager defaultManager]; - if(![fm fileExistsAtPath:fileDir]) { - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtPath:fileDir withIntermediateDirectories:true attributes:nil error:&error]; - if (error) { - _result(getFlutterError(error)); - return nil; - } - } - - return file; -} - - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer @@ -193,6 +174,7 @@ @interface FLTCam : NSObject _videoWriter.status == AVAssetWriterStatusCompleted) { - result(nil); + result(self->_videoRecordingPath); + self->_videoRecordingPath = nil; } else { [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; } @@ -868,7 +882,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; + [_camera startVideoRecordingWithResult:result]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { [_camera stopVideoRecordingWithResult:result]; } else { diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 80862495888f..7179052f64e4 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -321,17 +321,11 @@ class CameraController extends ValueNotifier { _imageStreamSubscription = null; } - /// Start a video recording and save the file to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as soon as [stopVideoRecording] returns. + /// Start a video recording. /// + /// The file can be read as soon as [stopVideoRecording] returns it. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { + Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -352,16 +346,17 @@ class CameraController extends ValueNotifier { } try { - XFile file = await CameraPlatform.instance.startVideoRecording(_cameraId); + await CameraPlatform.instance.startVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } - /// Stop recording. - Future stopVideoRecording() async { + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -375,8 +370,9 @@ class CameraController extends ValueNotifier { ); } try { - await CameraPlatform.instance.stopVideoRecording(_cameraId); + XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: false); + return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 4b62842e30b4..c398e9e9ef17 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -93,12 +93,8 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stops the video recording. - /// - /// When the [stopVideoRecording] method completes successfully the recorded - /// video can be accessed through the file returned by the - /// [startVideoRecording] method. - Future stopVideoRecording(int cameraId) { + /// Stops the video recording and returns the file where it was saved. + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 1b74948043f521929cd51fadfa416f044176c807 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 12:55:06 +0100 Subject: [PATCH 32/67] Update unit tests --- .../test/method_channel_camera_test.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 4f9e65e67f82..009be3846088 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -319,15 +319,15 @@ void main() { ]); }); - test('Should start recording a video and return an XFile instance', - () async { + test('Should start recording a video', () async { // Arrange MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': '/test/path.mkv'}); + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); // Act - XFile file = await camera.startVideoRecording(cameraId); + await camera.startVideoRecording(cameraId); // Assert expect(channel.log, [ @@ -335,18 +335,17 @@ void main() { 'cameraId': cameraId, }), ]); - expect(file.path, '/test/path.mkv'); }); - test('Should stop a video recording', () async { + test('Should stop a video recording and return the file', () async { // Arrange MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': null}, + methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act - await camera.stopVideoRecording(cameraId); + XFile file = await camera.stopVideoRecording(cameraId); // Assert expect(channel.log, [ @@ -354,6 +353,7 @@ void main() { 'cameraId': cameraId, }), ]); + expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { From fe00999e689fbdde839193cf164b9d2602b76784 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 13:04:45 +0100 Subject: [PATCH 33/67] Update error handling of video methods in iOS code. Make iOS code more consistent. --- .../camera/camera/ios/Classes/CameraPlugin.m | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 862e2b613f24..ecaad73a57de 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -580,7 +580,7 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { return; } if (![self setupWriterForPath:_videoRecordingPath]) { - [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; + result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); return; } _isRecording = YES; @@ -591,7 +591,7 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { _audioIsDisconnected = NO; result(nil); } else { - [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; + result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); } } @@ -604,7 +604,7 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { result(self->_videoRecordingPath); self->_videoRecordingPath = nil; } else { - [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + result([FlutterError errorWithCode:@"IOError" message:@"AVAssetWriter could not finish writing!" details:nil]); } }]; } @@ -617,14 +617,16 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { } } -- (void)pauseVideoRecording { +- (void)pauseVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = YES; _videoIsDisconnected = YES; _audioIsDisconnected = YES; + result(nil); } -- (void)resumeVideoRecording { +- (void)resumeVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = NO; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { @@ -841,12 +843,6 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else if ([@"stopImageStream" isEqualToString:call.method]) { [_camera stopImageStream]; result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecording]; - result(nil); - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecording]; - result(nil); } else { NSDictionary *argsMap = call.arguments; NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; @@ -885,6 +881,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera startVideoRecordingWithResult:result]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { [_camera stopVideoRecordingWithResult:result]; + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecordingWithResult:result]; + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecordingWithResult:result]; } else { result(FlutterMethodNotImplemented); } From 8c3cc3b745cfa3cf066e2da741d15f919a2cd55e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:29:45 +0100 Subject: [PATCH 34/67] Updated startVideoRecording documentation --- packages/camera/camera/lib/camera.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 7179052f64e4..ddce736d8ce4 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -323,7 +323,7 @@ class CameraController extends ValueNotifier { /// Start a video recording. /// - /// The file can be read as soon as [stopVideoRecording] returns it. + /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { From a6441f4883c06120f3844ebb5f91d690ea4c7c71 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:59:52 +0100 Subject: [PATCH 35/67] Make sure file is returned by stopVideoRecording --- .../main/java/io/flutter/plugins/camera/Camera.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index a6479953450a..63bd8623f3a0 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -66,6 +66,7 @@ public class Camera { private CaptureRequest.Builder captureRequestBuilder; private MediaRecorder mediaRecorder; private boolean recordingVideo; + private File videoRecordingFile; private CamcorderProfile recordingProfile; private int currentOrientation = ORIENTATION_UNKNOWN; private Context applicationContext; @@ -342,21 +343,22 @@ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession public void startVideoRecording(Result result) { final File outputDir = applicationContext.getCacheDir(); - final File file; try { - file = File.createTempFile("REC", ".mp4", outputDir); + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); } catch (IOException e) { result.error("cannotCreateFile", e.getMessage(), null); return; } try { - prepareMediaRecorder(file.getAbsolutePath()); + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(file.getAbsolutePath()); + result.success(null); } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; result.error("videoRecordingFailed", e.getMessage(), null); } } @@ -372,7 +374,8 @@ public void stopVideoRecording(@NonNull final Result result) { mediaRecorder.stop(); mediaRecorder.reset(); startPreview(); - result.success(null); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; } catch (CameraAccessException | IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); } From 959fd3e0cb18c6fe51cc1c2b9f2dae0489c0dd55 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 16:04:46 +0100 Subject: [PATCH 36/67] Use correct event-type after initializing --- .../java/io/flutter/plugins/camera/Camera.java | 2 +- .../io/flutter/plugins/camera/DartMessenger.java | 7 +++---- .../plugins/camera/DartMessengerTest.java | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 63bd8623f3a0..4cddaa2e3b68 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -163,7 +163,7 @@ public void onOpened(@NonNull CameraDevice device) { return; } - dartMessenger.sendInitializedEvent( + dartMessenger.sendCameraInitializedEvent( previewSize.getWidth(), previewSize.getHeight()); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index bbe4f374ecf6..1d5166222ea2 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -14,19 +14,18 @@ class DartMessenger { @Nullable private MethodChannel channel; - enum EventType { ERROR, CAMERA_CLOSING, - RESOLUTION_CHANGED, + CAMERA_INITIALIZED, } DartMessenger(BinaryMessenger messenger, long cameraId) { channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); } - void sendInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ + void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + this.send(EventType.CAMERA_INITIALIZED, new HashMap() {{ if (previewWidth != null) put("previewWidth", previewWidth); if (previewHeight != null) put("previewHeight", previewHeight); }}); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 5a5358229c15..42e81986271c 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,6 +7,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugins.camera.DartMessenger.EventType; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -79,6 +80,21 @@ public void send_includesErrorDescriptions() { assertEquals("error description", event.get("errorDescription")); } + @Test + public void sendCameraInitializedEvent() { + initializeEventSink(); + + dartMessenger.sendCameraInitializedEvent(0, 0); + + List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(1, sentMessages.size()); + Map event = decodeSentMessage(sentMessages.get(0)); + assertEquals( + EventType.CAMERA_INITIALIZED.toString().toLowerCase(), event.get("eventType")); + assertNull(event.get("previewWidth")); + assertNull(event.get("previewHeight")); + } + @Test public void sendCameraClosingEvent() { initializeEventSink(); From b8eeb05c9aa28c35cae4051b9c52b00034f3ca85 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 19:22:43 +0100 Subject: [PATCH 37/67] Fix DartMessenger unit-tests --- .../flutter/plugins/camera/DartMessenger.java | 8 +-- .../plugins/camera/DartMessengerTest.java | 72 +++++-------------- packages/camera/camera/lib/camera.dart | 11 +-- .../method_channel/method_channel_camera.dart | 8 ++- 4 files changed, 34 insertions(+), 65 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 1d5166222ea2..89939a3e2fb0 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -17,7 +17,7 @@ class DartMessenger { enum EventType { ERROR, CAMERA_CLOSING, - CAMERA_INITIALIZED, + INITIALIZED, } DartMessenger(BinaryMessenger messenger, long cameraId) { @@ -25,9 +25,9 @@ enum EventType { } void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.CAMERA_INITIALIZED, new HashMap() {{ - if (previewWidth != null) put("previewWidth", previewWidth); - if (previewHeight != null) put("previewHeight", previewHeight); + this.send(EventType.INITIALIZED, new HashMap() {{ + if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); + if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); }}); } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 42e81986271c..89257d2ded04 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -2,8 +2,8 @@ import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; @@ -11,33 +11,26 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.junit.Before; import org.junit.Test; public class DartMessengerTest { /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ private static class FakeBinaryMessenger implements BinaryMessenger { - private BinaryMessageHandler handler; private final List sentMessages = new ArrayList<>(); @Override - public void send(String channel, ByteBuffer message) { + public void send(@NonNull String channel, ByteBuffer message) { sentMessages.add(message); } @Override - public void send(String channel, ByteBuffer message, BinaryReply callback) { + public void send(@NonNull String channel, ByteBuffer message, BinaryReply callback) { send(channel, message); } @Override - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - this.handler = handler; - } - - BinaryMessageHandler getMessageHandler() { - return handler; + public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) { } List getMessages() { @@ -55,70 +48,43 @@ public void setUp() { } @Test - public void setsStreamHandler() { - assertNotNull(fakeBinaryMessenger.getMessageHandler()); - } - - @Test - public void send_handlesNullEventSinks() { - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); - - List sentMessages = fakeBinaryMessenger.getMessages(); - assertEquals(0, sentMessages.size()); - } - - @Test - public void send_includesErrorDescriptions() { - initializeEventSink(); - - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + public void sendCameraErrorEvent_includesErrorDescriptions() { + dartMessenger.sendCameraErrorEvent("error description"); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), event.get("eventType")); - assertEquals("error description", event.get("errorDescription")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), call.method); + assertEquals("error description", call.argument("description")); } @Test - public void sendCameraInitializedEvent() { - initializeEventSink(); - + public void sendCameraInitializedEvent_includesPreviewSize() { dartMessenger.sendCameraInitializedEvent(0, 0); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals( - EventType.CAMERA_INITIALIZED.toString().toLowerCase(), event.get("eventType")); - assertNull(event.get("previewWidth")); - assertNull(event.get("previewHeight")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals(EventType.INITIALIZED.toString().toLowerCase(), call.method); + assertEquals(0, (double)call.argument("previewWidth"), 0); + assertEquals(0, (double)call.argument("previewHeight"), 0); } @Test public void sendCameraClosingEvent() { - initializeEventSink(); - dartMessenger.sendCameraClosingEvent(); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); + MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals( - DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), event.get("eventType")); - assertNull(event.get("errorDescription")); + DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); + assertNull(call.argument("description")); } - @SuppressWarnings("unchecked") - private Map decodeSentMessage(ByteBuffer sentMessage) { + private MethodCall decodeSentMessage(ByteBuffer sentMessage) { sentMessage.position(0); - return (Map) StandardMethodCodec.INSTANCE.decodeEnvelope(sentMessage); - } - private void initializeEventSink() { - MethodCall call = new MethodCall("listen", null); - ByteBuffer encodedCall = StandardMethodCodec.INSTANCE.encodeMethodCall(call); - encodedCall.position(0); - fakeBinaryMessenger.getMessageHandler().onMessage(encodedCall, reply -> {}); + return StandardMethodCodec.INSTANCE.decodeMethodCall(sentMessage); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index ddce736d8ce4..fa11c1108061 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -189,11 +189,12 @@ class CameraController extends ValueNotifier { unawaited( CameraPlatform.instance .onCameraInitialized(_cameraId) - .map((event) => Size( - event.previewWidth, - event.previewHeight, - )) - + .map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }) .first .then((previewSize) => _resolutionCompleter.complete(previewSize)), ); diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index bc836b0ac98e..87b8e44b05b2 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -29,9 +29,11 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); - Stream _events(int cameraId) => - cameraEventStreamController.stream - .where((event) => event.cameraId == cameraId); + Stream _events(int cameraId) => cameraEventStreamController + .stream + .tap((event) => print( + 'Event received, EVENT TYPE: ${event.runtimeType}, STRING: ${event.toString()}')) + .where((event) => event.cameraId == cameraId); @override Future> availableCameras() async { From e34caa452d67fc384e8ca62dc2ac393c4b69c1a4 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 16:01:31 +0100 Subject: [PATCH 38/67] Change cast --- packages/camera/camera/ios/Classes/CameraPlugin.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index ecaad73a57de..72acbb1369b7 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -853,8 +853,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re }; FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - (long long) cameraId] + stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", + (unsigned long) cameraId] binaryMessenger:_messenger]; _camera.methodChannel = methodChannel; [methodChannel invokeMethod:@"initialized" arguments:@{ From f1fb5f06e06c9388f4672b9968e47a680e00e365 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 19:26:23 +0100 Subject: [PATCH 39/67] Fix formatting --- .../io/flutter/plugins/camera/Camera.java | 880 ++++++----- .../flutter/plugins/camera/DartMessenger.java | 82 +- .../plugins/camera/MethodCallHandlerImpl.java | 29 +- .../plugins/camera/DartMessengerTest.java | 10 +- .../camera/camera/ios/Classes/CameraPlugin.m | 1388 +++++++++-------- packages/camera/camera/test/camera_test.dart | 3 - 6 files changed, 1206 insertions(+), 1186 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 4cddaa2e3b68..5ac1dac805bb 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -35,7 +35,6 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -48,479 +47,478 @@ import java.util.concurrent.Executors; public class Camera { - private final SurfaceTextureEntry flutterTexture; - private final CameraManager cameraManager; - private final OrientationEventListener orientationEventListener; - private final boolean isFrontFacing; - private final int sensorOrientation; - private final String cameraName; - private final Size captureSize; - private final Size previewSize; - private final boolean enableAudio; - - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private ImageReader pictureImageReader; - private ImageReader imageStreamReader; - private DartMessenger dartMessenger; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; - private boolean recordingVideo; - private File videoRecordingFile; - private CamcorderProfile recordingProfile; - private int currentOrientation = ORIENTATION_UNKNOWN; - private Context applicationContext; - - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, + private final SurfaceTextureEntry flutterTexture; + private final CameraManager cameraManager; + private final OrientationEventListener orientationEventListener; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final String cameraName; + private final Size captureSize; + private final Size previewSize; + private final boolean enableAudio; + + private CameraDevice cameraDevice; + private CameraCaptureSession cameraCaptureSession; + private ImageReader pictureImageReader; + private ImageReader imageStreamReader; + private DartMessenger dartMessenger; + private CaptureRequest.Builder captureRequestBuilder; + private MediaRecorder mediaRecorder; + private boolean recordingVideo; + private File videoRecordingFile; + private CamcorderProfile recordingProfile; + private int currentOrientation = ORIENTATION_UNKNOWN; + private Context applicationContext; + + // Mirrors camera.dart + public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, + } + + public Camera( + final Activity activity, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, + final String cameraName, + final String resolutionPreset, + final boolean enableAudio) + throws CameraAccessException { + if (activity == null) { + throw new IllegalStateException("No activity available!"); } - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final String cameraName, - final String resolutionPreset, - final boolean enableAudio) - throws CameraAccessException { - if (activity == null) { - throw new IllegalStateException("No activity available!"); - } - this.cameraName = cameraName; - this.enableAudio = enableAudio; - this.flutterTexture = flutterTexture; - this.dartMessenger = dartMessenger; - this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); - this.applicationContext = activity.getApplicationContext(); - orientationEventListener = - new OrientationEventListener(activity.getApplicationContext()) { - @Override - public void onOrientationChanged(int i) { - if (i == ORIENTATION_UNKNOWN) { - return; - } - // Convert the raw deg angle to the nearest multiple of 90. - currentOrientation = (int) Math.round(i / 90.0) * 90; - } - }; - orientationEventListener.enable(); - - CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - //noinspection ConstantConditions - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - //noinspection ConstantConditions - isFrontFacing = - characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); - recordingProfile = - CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); - captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); - previewSize = computeBestPreviewSize(cameraName, preset); + this.cameraName = cameraName; + this.enableAudio = enableAudio; + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; + this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + orientationEventListener = + new OrientationEventListener(activity.getApplicationContext()) { + @Override + public void onOrientationChanged(int i) { + if (i == ORIENTATION_UNKNOWN) { + return; + } + // Convert the raw deg angle to the nearest multiple of 90. + currentOrientation = (int) Math.round(i / 90.0) * 90; + } + }; + orientationEventListener.enable(); + + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + StreamConfigurationMap streamConfigurationMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + //noinspection ConstantConditions + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + //noinspection ConstantConditions + isFrontFacing = + characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = + CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + previewSize = computeBestPreviewSize(cameraName, preset); + } + + private void prepareMediaRecorder(String outputFilePath) throws IOException { + if (mediaRecorder != null) { + mediaRecorder.release(); } - private void prepareMediaRecorder(String outputFilePath) throws IOException { - if (mediaRecorder != null) { - mediaRecorder.release(); - } + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } + + @SuppressLint("MissingPermission") + public void open() throws CameraAccessException { + pictureImageReader = + ImageReader.newInstance( + captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); + + // Used to steam image byte data to dart side. + imageStreamReader = + ImageReader.newInstance( + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + + cameraManager.openCamera( + cameraName, + new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice device) { + cameraDevice = device; + try { + startPreview(); + } catch (CameraAccessException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + close(); + return; + } - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); + dartMessenger.sendCameraInitializedEvent( + previewSize.getWidth(), previewSize.getHeight()); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + dartMessenger.sendCameraClosingEvent(); + super.onClosed(camera); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + close(); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { + close(); + String errorDescription; + switch (errorCode) { + case ERROR_CAMERA_IN_USE: + errorDescription = "The camera device is in use already."; + break; + case ERROR_MAX_CAMERAS_IN_USE: + errorDescription = "Max cameras in use"; + break; + case ERROR_CAMERA_DISABLED: + errorDescription = "The camera device could not be opened due to a device policy."; + break; + case ERROR_CAMERA_DEVICE: + errorDescription = "The camera device has encountered a fatal error"; + break; + case ERROR_CAMERA_SERVICE: + errorDescription = "The camera service has encountered a fatal error."; + break; + default: + errorDescription = "Unknown camera error"; + } + dartMessenger.sendCameraErrorEvent(errorDescription); + } + }, + null); + } + + private void writeToFile(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + while (0 < buffer.remaining()) { + outputStream.getChannel().write(buffer); + } } - - @SuppressLint("MissingPermission") - public void open() throws CameraAccessException { - pictureImageReader = - ImageReader.newInstance( - captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); - - // Used to steam image byte data to dart side. - imageStreamReader = - ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); - - cameraManager.openCamera( - cameraName, - new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice device) { - cameraDevice = device; - try { - startPreview(); - } catch (CameraAccessException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - close(); - return; - } - - dartMessenger.sendCameraInitializedEvent( - previewSize.getWidth(), - previewSize.getHeight()); - } - - @Override - public void onClosed(@NonNull CameraDevice camera) { - dartMessenger.sendCameraClosingEvent(); - super.onClosed(camera); - } - - @Override - public void onDisconnected(@NonNull CameraDevice cameraDevice) { - close(); - dartMessenger.sendCameraErrorEvent("The camera was disconnected."); - } - - @Override - public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { - close(); - String errorDescription; - switch (errorCode) { - case ERROR_CAMERA_IN_USE: - errorDescription = "The camera device is in use already."; - break; - case ERROR_MAX_CAMERAS_IN_USE: - errorDescription = "Max cameras in use"; - break; - case ERROR_CAMERA_DISABLED: - errorDescription = "The camera device could not be opened due to a device policy."; - break; - case ERROR_CAMERA_DEVICE: - errorDescription = "The camera device has encountered a fatal error"; - break; - case ERROR_CAMERA_SERVICE: - errorDescription = "The camera service has encountered a fatal error."; - break; - default: - errorDescription = "Unknown camera error"; - } - dartMessenger.sendCameraErrorEvent(errorDescription); - } - }, - null); + } + + SurfaceTextureEntry getFlutterTexture() { + return flutterTexture; + } + + public void takePicture(@NonNull final Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; } - private void writeToFile(ByteBuffer buffer, File file) throws IOException { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - while (0 < buffer.remaining()) { - outputStream.getChannel().write(buffer); + pictureImageReader.setOnImageAvailableListener( + reader -> { + try (Image image = reader.acquireLatestImage()) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + writeToFile(buffer, file); + result.success(file.getAbsolutePath()); + } catch (IOException e) { + result.error("IOError", "Failed saving image", null); + } + }, + null); + + try { + final CaptureRequest.Builder captureBuilder = + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(pictureImageReader.getSurface()); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + result.error("captureFailure", reason, null); } - } + }, + null); + } catch (CameraAccessException e) { + result.error("cameraAccess", e.getMessage(), null); } - - SurfaceTextureEntry getFlutterTexture() { - return flutterTexture; + } + + private void createCaptureSession(int templateType, Surface... surfaces) + throws CameraAccessException { + createCaptureSession(templateType, null, surfaces); + } + + private void createCaptureSession( + int templateType, Runnable onSuccessCallback, Surface... surfaces) + throws CameraAccessException { + // Close any existing capture session. + closeCaptureSession(); + + // Create a new capture builder. + captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); + + // Build Flutter surface to render to + SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface flutterSurface = new Surface(surfaceTexture); + captureRequestBuilder.addTarget(flutterSurface); + + List remainingSurfaces = Arrays.asList(surfaces); + if (templateType != CameraDevice.TEMPLATE_PREVIEW) { + // If it is not preview mode, add all surfaces as targets. + for (Surface surface : remainingSurfaces) { + captureRequestBuilder.addTarget(surface); + } } - public void takePicture(@NonNull final Result result) { - final File outputDir = applicationContext.getCacheDir(); - final File file; - try { - file = File.createTempFile("CAP", ".jpg", outputDir); - } catch (IOException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; - } - - pictureImageReader.setOnImageAvailableListener( - reader -> { - try (Image image = reader.acquireLatestImage()) { - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - writeToFile(buffer, file); - result.success(file.getAbsolutePath()); - } catch (IOException e) { - result.error("IOError", "Failed saving image", null); - } - }, - null); - - try { - final CaptureRequest.Builder captureBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureBuilder.addTarget(pictureImageReader.getSurface()); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); - } - }, - null); - } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); - } + // Prepare the callback + CameraCaptureSession.StateCallback callback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + if (cameraDevice == null) { + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); + return; + } + cameraCaptureSession = session; + captureRequestBuilder.set( + CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + if (onSuccessCallback != null) { + onSuccessCallback.run(); + } + } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); + } + }; + + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + // Start the session + cameraDevice.createCaptureSession(surfaceList, callback, null); + } + + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + try { + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; } - private void createCaptureSession(int templateType, Surface... surfaces) - throws CameraAccessException { - createCaptureSession(templateType, null, surfaces); + try { + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); + recordingVideo = true; + createCaptureSession( + CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + result.success(null); + } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; + result.error("videoRecordingFailed", e.getMessage(), null); } + } - private void createCaptureSession( - int templateType, Runnable onSuccessCallback, Surface... surfaces) - throws CameraAccessException { - // Close any existing capture session. - closeCaptureSession(); - - // Create a new capture builder. - captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); - - // Build Flutter surface to render to - SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); - surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface flutterSurface = new Surface(surfaceTexture); - captureRequestBuilder.addTarget(flutterSurface); - - List remainingSurfaces = Arrays.asList(surfaces); - if (templateType != CameraDevice.TEMPLATE_PREVIEW) { - // If it is not preview mode, add all surfaces as targets. - for (Surface surface : remainingSurfaces) { - captureRequestBuilder.addTarget(surface); - } - } - - // Prepare the callback - CameraCaptureSession.StateCallback callback = - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - try { - if (cameraDevice == null) { - dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); - return; - } - cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - if (onSuccessCallback != null) { - onSuccessCallback.run(); - } - } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); - } - }; - - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); - // Start the session - cameraDevice.createCaptureSession(surfaceList, callback, null); + public void stopVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void startVideoRecording(Result result) { - final File outputDir = applicationContext.getCacheDir(); - try { - videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); - } catch (IOException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; - } - - try { - prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); - recordingVideo = true; - createCaptureSession( - CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(null); - } catch (CameraAccessException | IOException e) { - recordingVideo = false; - videoRecordingFile = null; - result.error("videoRecordingFailed", e.getMessage(), null); - } + try { + recordingVideo = false; + mediaRecorder.stop(); + mediaRecorder.reset(); + startPreview(); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; + } catch (CameraAccessException | IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); } + } - public void stopVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - recordingVideo = false; - mediaRecorder.stop(); - mediaRecorder.reset(); - startPreview(); - result.success(videoRecordingFile.getAbsolutePath()); - videoRecordingFile = null; - } catch (CameraAccessException | IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - } + public void pauseVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void pauseVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } - - result.success(null); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.pause(); + } else { + result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; } - public void resumeVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - } else { - result.error( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } + result.success(null); + } - result.success(null); + public void resumeVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void startPreview() throws CameraAccessException { - if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; - - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.resume(); + } else { + result.error( + "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; } - public void startPreviewWithImageStream(EventChannel imageStreamChannel) - throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); - - imageStreamChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink imageStreamSink) { - setImageStreamImageAvailableListener(imageStreamSink); - } - - @Override - public void onCancel(Object o) { - imageStreamReader.setOnImageAvailableListener(null, null); - } - }); + result.success(null); + } + + public void startPreview() throws CameraAccessException { + if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; + + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); + } + + public void startPreviewWithImageStream(EventChannel imageStreamChannel) + throws CameraAccessException { + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); + + imageStreamChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink imageStreamSink) { + setImageStreamImageAvailableListener(imageStreamSink); + } + + @Override + public void onCancel(Object o) { + imageStreamReader.setOnImageAvailableListener(null, null); + } + }); + } + + private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { + imageStreamReader.setOnImageAvailableListener( + reader -> { + Image img = reader.acquireLatestImage(); + if (img == null) return; + + List> planes = new ArrayList<>(); + for (Image.Plane plane : img.getPlanes()) { + ByteBuffer buffer = plane.getBuffer(); + + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes, 0, bytes.length); + + Map planeBuffer = new HashMap<>(); + planeBuffer.put("bytesPerRow", plane.getRowStride()); + planeBuffer.put("bytesPerPixel", plane.getPixelStride()); + planeBuffer.put("bytes", bytes); + + planes.add(planeBuffer); + } + + Map imageBuffer = new HashMap<>(); + imageBuffer.put("width", img.getWidth()); + imageBuffer.put("height", img.getHeight()); + imageBuffer.put("format", img.getFormat()); + imageBuffer.put("planes", planes); + + imageStreamSink.success(imageBuffer); + img.close(); + }, + null); + } + + private void closeCaptureSession() { + if (cameraCaptureSession != null) { + cameraCaptureSession.close(); + cameraCaptureSession = null; } + } - private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { - imageStreamReader.setOnImageAvailableListener( - reader -> { - Image img = reader.acquireLatestImage(); - if (img == null) return; - - List> planes = new ArrayList<>(); - for (Image.Plane plane : img.getPlanes()) { - ByteBuffer buffer = plane.getBuffer(); - - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes, 0, bytes.length); - - Map planeBuffer = new HashMap<>(); - planeBuffer.put("bytesPerRow", plane.getRowStride()); - planeBuffer.put("bytesPerPixel", plane.getPixelStride()); - planeBuffer.put("bytes", bytes); - - planes.add(planeBuffer); - } - - Map imageBuffer = new HashMap<>(); - imageBuffer.put("width", img.getWidth()); - imageBuffer.put("height", img.getHeight()); - imageBuffer.put("format", img.getFormat()); - imageBuffer.put("planes", planes); - - imageStreamSink.success(imageBuffer); - img.close(); - }, - null); - } + public void close() { + closeCaptureSession(); - private void closeCaptureSession() { - if (cameraCaptureSession != null) { - cameraCaptureSession.close(); - cameraCaptureSession = null; - } + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; } - - public void close() { - closeCaptureSession(); - - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - } - if (pictureImageReader != null) { - pictureImageReader.close(); - pictureImageReader = null; - } - if (imageStreamReader != null) { - imageStreamReader.close(); - imageStreamReader = null; - } - if (mediaRecorder != null) { - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; - } + if (pictureImageReader != null) { + pictureImageReader.close(); + pictureImageReader = null; } - - public void dispose() { - close(); - flutterTexture.release(); - orientationEventListener.disable(); + if (imageStreamReader != null) { + imageStreamReader.close(); + imageStreamReader = null; } - - private int getMediaOrientation() { - final int sensorOrientationOffset = - (currentOrientation == ORIENTATION_UNKNOWN) - ? 0 - : (isFrontFacing) ? -currentOrientation : currentOrientation; - return (sensorOrientationOffset + sensorOrientation + 360) % 360; + if (mediaRecorder != null) { + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; } + } + + public void dispose() { + close(); + flutterTexture.release(); + orientationEventListener.disable(); + } + + private int getMediaOrientation() { + final int sensorOrientationOffset = + (currentOrientation == ORIENTATION_UNKNOWN) + ? 0 + : (isFrontFacing) ? -currentOrientation : currentOrientation; + return (sensorOrientationOffset + sensorOrientation + 360) % 360; + } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 89939a3e2fb0..49f9d9a76de0 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -1,54 +1,58 @@ package io.flutter.plugins.camera; import android.text.TextUtils; - import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.Map; class DartMessenger { - @Nullable - private MethodChannel channel; - - enum EventType { - ERROR, - CAMERA_CLOSING, - INITIALIZED, - } - - DartMessenger(BinaryMessenger messenger, long cameraId) { - channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); - } - - void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.INITIALIZED, new HashMap() {{ + @Nullable private MethodChannel channel; + + enum EventType { + ERROR, + CAMERA_CLOSING, + INITIALIZED, + } + + DartMessenger(BinaryMessenger messenger, long cameraId) { + channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); + } + + void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + this.send( + EventType.INITIALIZED, + new HashMap() { + { if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); - }}); - } - - void sendCameraClosingEvent() { - send(EventType.CAMERA_CLOSING); - } - - void sendCameraErrorEvent(@Nullable String description) { - this.send(EventType.ERROR, new HashMap() {{ + } + }); + } + + void sendCameraClosingEvent() { + send(EventType.CAMERA_CLOSING); + } + + void sendCameraErrorEvent(@Nullable String description) { + this.send( + EventType.ERROR, + new HashMap() { + { if (!TextUtils.isEmpty(description)) put("description", description); - }}); - } + } + }); + } - void send(EventType eventType) { - send(eventType, new HashMap<>()); - } + void send(EventType eventType) { + send(eventType, new HashMap<>()); + } - void send(EventType eventType, Map args) { - if (channel == null) { - return; - } - channel.invokeMethod(eventType.toString().toLowerCase(), args); + void send(EventType eventType, Map args) { + if (channel == null) { + return; } + channel.invokeMethod(eventType.toString().toLowerCase(), args); + } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index c2125a475dca..6c2e65e76f9e 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -74,22 +74,23 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) }); break; } - case "initialize": { - if (camera != null) { - try { - camera.open(); - result.success(null); - } catch (Exception e) { - handleException(e, result); + case "initialize": + { + if (camera != null) { + try { + camera.open(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error( + "cameraNotFound", + "Camera not found. Please call the 'create' method before calling 'initialize'.", + null); } - } else { - result.error( - "cameraNotFound", - "Camera not found. Please call the 'create' method before calling 'initialize'.", - null); + break; } - break; - } case "takePicture": { camera.takePicture(result); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 89257d2ded04..3f182280a983 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -30,8 +30,7 @@ public void send(@NonNull String channel, ByteBuffer message, BinaryReply callba } @Override - public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) { - } + public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) {} List getMessages() { return new ArrayList<>(sentMessages); @@ -66,8 +65,8 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals(EventType.INITIALIZED.toString().toLowerCase(), call.method); - assertEquals(0, (double)call.argument("previewWidth"), 0); - assertEquals(0, (double)call.argument("previewHeight"), 0); + assertEquals(0, (double) call.argument("previewWidth"), 0); + assertEquals(0, (double) call.argument("previewHeight"), 0); } @Test @@ -77,8 +76,7 @@ public void sendCameraClosingEvent() { List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); - assertEquals( - DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); + assertEquals(DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); assertNull(call.argument("description")); } diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 72acbb1369b7..dbff59e64f9a 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -10,9 +10,9 @@ #import static FlutterError *getFlutterError(NSError *error) { - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.localizedDescription - details:error.domain]; + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] + message:error.localizedDescription + details:error.domain]; } @interface FLTSavePhotoDelegate : NSObject @@ -29,132 +29,131 @@ @interface FLTImageStreamHandler : NSObject @implementation FLTImageStreamHandler - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - return nil; + _eventSink = nil; + return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; + _eventSink = events; + return nil; } @end @implementation FLTSavePhotoDelegate { - /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. - FLTSavePhotoDelegate *selfReference; + /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. + FLTSavePhotoDelegate *selfReference; } - initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager - cameraPosition:(AVCaptureDevicePosition)cameraPosition { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _path = path; - _motionManager = motionManager; - _cameraPosition = cameraPosition; - selfReference = self; - _result = result; - return self; + result:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager + cameraPosition:(AVCaptureDevicePosition)cameraPosition { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _path = path; + _motionManager = motionManager; + _cameraPosition = cameraPosition; + selfReference = self; + _result = result; + return self; } - (void)captureOutput:(AVCapturePhotoOutput *)output -didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer -previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer - resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings - bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings - error:(NSError *)error API_AVAILABLE(ios(10)) { - selfReference = nil; - if (error) { - _result(getFlutterError(error)); - return; - } - NSData *data = [AVCapturePhotoOutput - JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer - previewPhotoSampleBuffer:previewPhotoSampleBuffer]; - UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage - scale:1.0 - orientation:[self getImageRotation]]; - // TODO(sigurdm): Consider writing file asynchronously. - bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; - if (!success) { - _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); - return; - } - _result(_path); + didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer + previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer + resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings + bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings + error:(NSError *)error API_AVAILABLE(ios(10)) { + selfReference = nil; + if (error) { + _result(getFlutterError(error)); + return; + } + NSData *data = [AVCapturePhotoOutput + JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer + previewPhotoSampleBuffer:previewPhotoSampleBuffer]; + UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage + scale:1.0 + orientation:[self getImageRotation]]; + // TODO(sigurdm): Consider writing file asynchronously. + bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; + if (!success) { + _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); + return; + } + _result(_path); } - (UIImageOrientation)getImageRotation { - float const threshold = 45.0; - BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { - return fabsf(value1 - value2) < threshold; - }; - BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { - return isNearValue(fabsf(value1), fabsf(value2)); - }; - float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, - _motionManager.accelerometerData.acceleration.x)) * - 180 / M_PI; - if (isNearValue(-90.0, yxAtan)) { - return UIImageOrientationRight; - } else if (isNearValueABS(180.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp - : UIImageOrientationDown; - } else if (isNearValueABS(0.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ - : UIImageOrientationUp /*do not rotate*/; - } else if (isNearValue(90.0, yxAtan)) { - return UIImageOrientationLeft; - } - // If none of the above, then the device is likely facing straight down or straight up -- just - // pick something arbitrary - // TODO: Maybe use the UIInterfaceOrientation if in these scenarios - return UIImageOrientationUp; + float const threshold = 45.0; + BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { + return fabsf(value1 - value2) < threshold; + }; + BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { + return isNearValue(fabsf(value1), fabsf(value2)); + }; + float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, + _motionManager.accelerometerData.acceleration.x)) * + 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { + return UIImageOrientationRight; + } else if (isNearValueABS(180.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp + : UIImageOrientationDown; + } else if (isNearValueABS(0.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ + : UIImageOrientationUp /*do not rotate*/; + } else if (isNearValue(90.0, yxAtan)) { + return UIImageOrientationLeft; + } + // If none of the above, then the device is likely facing straight down or straight up -- just + // pick something arbitrary + // TODO: Maybe use the UIInterfaceOrientation if in these scenarios + return UIImageOrientationUp; } @end // Mirrors ResolutionPreset in camera.dart typedef enum { - veryLow, - low, - medium, - high, - veryHigh, - ultraHigh, - max, + veryLow, + low, + medium, + high, + veryHigh, + ultraHigh, + max, } ResolutionPreset; static ResolutionPreset getResolutionPresetForString(NSString *preset) { - if ([preset isEqualToString:@"veryLow"]) { - return veryLow; - } else if ([preset isEqualToString:@"low"]) { - return low; - } else if ([preset isEqualToString:@"medium"]) { - return medium; - } else if ([preset isEqualToString:@"high"]) { - return high; - } else if ([preset isEqualToString:@"veryHigh"]) { - return veryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return ultraHigh; - } else if ([preset isEqualToString:@"max"]) { - return max; - } else { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"Unknown resolution preset %@", preset] - }]; - @throw error; - } + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } } @interface FLTCam : NSObject + AVCaptureVideoDataOutputSampleBufferDelegate, + AVCaptureAudioDataOutputSampleBufferDelegate> @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; @@ -191,7 +190,7 @@ @interface FLTCam : NSObject 0) { + currentSampleTime = CMTimeAdd(currentSampleTime, dur); + } + + if (_audioIsDisconnected) { + _audioIsDisconnected = NO; + + if (_audioTimeOffset.value == 0) { + _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); } else { - CMTime dur = CMSampleBufferGetDuration(sampleBuffer); - - if (dur.value > 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); - } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { - CFRelease(sampleBuffer); - sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; - } - - [self newAudioSample:sampleBuffer]; + CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); } - + + return; + } + + _lastAudioSampleTime = currentSampleTime; + + if (_audioTimeOffset.value != 0) { CFRelease(sampleBuffer); + sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; + } + + [self newAudioSample:sampleBuffer]; } + + CFRelease(sampleBuffer); + } } - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED { - CMItemCount count; - CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); - CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); - CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); - for (CMItemCount i = 0; i < count; i++) { - pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); - pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); - } - CMSampleBufferRef sout; - CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); - free(pInfo); - return sout; + CMItemCount count; + CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); + CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); + CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); + for (CMItemCount i = 0; i < count; i++) { + pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); + pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); + } + CMSampleBufferRef sout; + CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); + free(pInfo); + return sout; } - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; - } + return; + } + if (_videoWriterInput.readyForMoreMediaData) { + if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel + invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } + } } - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; - } + return; + } + if (_audioWriterInput.readyForMoreMediaData) { + if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel + invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } + } } - (void)close { - [_captureSession stopRunning]; - for (AVCaptureInput *input in [_captureSession inputs]) { - [_captureSession removeInput:input]; - } - for (AVCaptureOutput *output in [_captureSession outputs]) { - [_captureSession removeOutput:output]; - } + [_captureSession stopRunning]; + for (AVCaptureInput *input in [_captureSession inputs]) { + [_captureSession removeInput:input]; + } + for (AVCaptureOutput *output in [_captureSession outputs]) { + [_captureSession removeOutput:output]; + } } - (void)dealloc { - if (_latestPixelBuffer) { - CFRelease(_latestPixelBuffer); - } - [_motionManager stopAccelerometerUpdates]; + if (_latestPixelBuffer) { + CFRelease(_latestPixelBuffer); + } + [_motionManager stopAccelerometerUpdates]; } - (CVPixelBufferRef)copyPixelBuffer { - CVPixelBufferRef pixelBuffer = _latestPixelBuffer; - while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { - pixelBuffer = _latestPixelBuffer; - } - - return pixelBuffer; + CVPixelBufferRef pixelBuffer = _latestPixelBuffer; + while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { + pixelBuffer = _latestPixelBuffer; + } + + return pixelBuffer; } - (void)startVideoRecordingWithResult:(FlutterResult)result { - if (!_isRecording) { - NSError *error; - _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" subfolder:@"videos" prefix:@"CAP_" error:error]; - if (error) { - result(getFlutterError(error)); - return; - } - if (![self setupWriterForPath:_videoRecordingPath]) { - result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); - return; - } - _isRecording = YES; - _isRecordingPaused = NO; - _videoTimeOffset = CMTimeMake(0, 1); - _audioTimeOffset = CMTimeMake(0, 1); - _videoIsDisconnected = NO; - _audioIsDisconnected = NO; - result(nil); - } else { - result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); + if (!_isRecording) { + NSError *error; + _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" + subfolder:@"videos" + prefix:@"CAP_" + error:error]; + if (error) { + result(getFlutterError(error)); + return; } + if (![self setupWriterForPath:_videoRecordingPath]) { + result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); + return; + } + _isRecording = YES; + _isRecordingPaused = NO; + _videoTimeOffset = CMTimeMake(0, 1); + _audioTimeOffset = CMTimeMake(0, 1); + _videoIsDisconnected = NO; + _audioIsDisconnected = NO; + result(nil); + } else { + result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); + } } - (void)stopVideoRecordingWithResult:(FlutterResult)result { - if (_isRecording) { - _isRecording = NO; - if (_videoWriter.status != AVAssetWriterStatusUnknown) { - [_videoWriter finishWritingWithCompletionHandler:^{ - if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - result(self->_videoRecordingPath); - self->_videoRecordingPath = nil; - } else { - result([FlutterError errorWithCode:@"IOError" message:@"AVAssetWriter could not finish writing!" details:nil]); - } - }]; + if (_isRecording) { + _isRecording = NO; + if (_videoWriter.status != AVAssetWriterStatusUnknown) { + [_videoWriter finishWritingWithCompletionHandler:^{ + if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { + result(self->_videoRecordingPath); + self->_videoRecordingPath = nil; + } else { + result([FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); } - } else { - NSError *error = + }]; + } + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(getFlutterError(error)); - } + result(getFlutterError(error)); + } } - (void)pauseVideoRecordingWithResult:(FlutterResult)result { - _isRecordingPaused = YES; - _videoIsDisconnected = YES; - _audioIsDisconnected = YES; - result(nil); + _isRecordingPaused = YES; + _videoIsDisconnected = YES; + _audioIsDisconnected = YES; + result(nil); } - (void)resumeVideoRecordingWithResult:(FlutterResult)result { - _isRecordingPaused = NO; - result(nil); + _isRecordingPaused = NO; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { - if (!_isStreamingImages) { - FlutterEventChannel *eventChannel = + if (!_isStreamingImages) { + FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; - - _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; - - _isStreamingImages = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; - } + + _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; + [eventChannel setStreamHandler:_imageStreamHandler]; + + _isStreamingImages = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; + } } - (void)stopImageStream { - if (_isStreamingImages) { - _isStreamingImages = NO; - _imageStreamHandler = nil; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; - } + if (_isStreamingImages) { + _isStreamingImages = NO; + _imageStreamHandler = nil; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; + } } - (BOOL)setupWriterForPath:(NSString *)path { - NSError *error = nil; - NSURL *outputURL; - if (path != nil) { - outputURL = [NSURL fileURLWithPath:path]; - } else { - return NO; - } - if (_enableAudio && !_isAudioSetup) { - [self setUpCaptureSessionForAudio]; - } - _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL - fileType:AVFileTypeQuickTimeMovie - error:&error]; - NSParameterAssert(_videoWriter); - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - return NO; - } - NSDictionary *videoSettings = [NSDictionary - dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, + NSError *error = nil; + NSURL *outputURL; + if (path != nil) { + outputURL = [NSURL fileURLWithPath:path]; + } else { + return NO; + } + if (_enableAudio && !_isAudioSetup) { + [self setUpCaptureSessionForAudio]; + } + _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL + fileType:AVFileTypeQuickTimeMovie + error:&error]; + NSParameterAssert(_videoWriter); + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + return NO; + } + NSDictionary *videoSettings = [NSDictionary + dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey, [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey, nil]; - _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:videoSettings]; - - _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor - assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput - sourcePixelBufferAttributes:@{ - (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) - }]; - - NSParameterAssert(_videoWriterInput); - _videoWriterInput.expectsMediaDataInRealTime = YES; - - // Add the audio input - if (_enableAudio) { - AudioChannelLayout acl; - bzero(&acl, sizeof(acl)); - acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - NSDictionary *audioOutputSettings = nil; - // Both type of audio inputs causes output video file to be corrupted. - audioOutputSettings = [NSDictionary - dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, - [NSNumber numberWithFloat:44100.0], AVSampleRateKey, - [NSNumber numberWithInt:1], AVNumberOfChannelsKey, - [NSData dataWithBytes:&acl length:sizeof(acl)], - AVChannelLayoutKey, nil]; - _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:audioOutputSettings]; - _audioWriterInput.expectsMediaDataInRealTime = YES; - - [_videoWriter addInput:_audioWriterInput]; - [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - } - - [_videoWriter addInput:_videoWriterInput]; - [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - - return YES; + _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoSettings]; + + _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor + assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput + sourcePixelBufferAttributes:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) + }]; + + NSParameterAssert(_videoWriterInput); + _videoWriterInput.expectsMediaDataInRealTime = YES; + + // Add the audio input + if (_enableAudio) { + AudioChannelLayout acl; + bzero(&acl, sizeof(acl)); + acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + NSDictionary *audioOutputSettings = nil; + // Both type of audio inputs causes output video file to be corrupted. + audioOutputSettings = [NSDictionary + dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, + [NSNumber numberWithFloat:44100.0], AVSampleRateKey, + [NSNumber numberWithInt:1], AVNumberOfChannelsKey, + [NSData dataWithBytes:&acl length:sizeof(acl)], + AVChannelLayoutKey, nil]; + _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio + outputSettings:audioOutputSettings]; + _audioWriterInput.expectsMediaDataInRealTime = YES; + + [_videoWriter addInput:_audioWriterInput]; + [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + } + + [_videoWriter addInput:_videoWriterInput]; + [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + + return YES; } - (void)setUpCaptureSessionForAudio { - NSError *error = nil; - // Create a device input with the device and add it to the session. - // Setup the audio input. - AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice - error:&error]; - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - } - // Setup the audio output. - _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; - - if ([_captureSession canAddInput:audioInput]) { - [_captureSession addInput:audioInput]; - - if ([_captureSession canAddOutput:_audioOutput]) { - [_captureSession addOutput:_audioOutput]; - _isAudioSetup = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; - _isAudioSetup = NO; - } + NSError *error = nil; + // Create a device input with the device and add it to the session. + // Setup the audio input. + AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice + error:&error]; + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + } + // Setup the audio output. + _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; + + if ([_captureSession canAddInput:audioInput]) { + [_captureSession addInput:audioInput]; + + if ([_captureSession canAddOutput:_audioOutput]) { + [_captureSession addOutput:_audioOutput]; + _isAudioSetup = YES; + } else { + [_methodChannel invokeMethod:@"error" + arguments:@"Unable to add Audio input/output to session capture"]; + _isAudioSetup = NO; } + } } @end @@ -749,146 +771,146 @@ @interface CameraPlugin () @end @implementation CameraPlugin { - dispatch_queue_t _dispatchQueue; + dispatch_queue_t _dispatchQueue; } + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" - binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" + binaryMessenger:[registrar messenger]]; + CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _registry = registry; - _messenger = messenger; - return self; + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = registry; + _messenger = messenger; + return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if (_dispatchQueue == nil) { - _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); - } - - // Invoke the plugin on another dispatch queue to avoid blocking the UI. - dispatch_async(_dispatchQueue, ^{ - [self handleMethodCallAsync:call result:result]; - }); + if (_dispatchQueue == nil) { + _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); + } + + // Invoke the plugin on another dispatch queue to avoid blocking the UI. + dispatch_async(_dispatchQueue, ^{ + [self handleMethodCallAsync:call result:result]; + }); } - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"availableCameras" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession - discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] - mediaType:AVMediaTypeVideo - position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = discoverySession.devices; - NSMutableArray *> *reply = - [[NSMutableArray alloc] initWithCapacity:devices.count]; - for (AVCaptureDevice *device in devices) { - NSString *lensFacing; - switch ([device position]) { - case AVCaptureDevicePositionBack: - lensFacing = @"back"; - break; - case AVCaptureDevicePositionFront: - lensFacing = @"front"; - break; - case AVCaptureDevicePositionUnspecified: - lensFacing = @"external"; - break; - } - [reply addObject:@{ - @"name" : [device uniqueID], - @"lensFacing" : lensFacing, - @"sensorOrientation" : @90, - }]; - } - result(reply); - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"create" isEqualToString:call.method]) { - NSString *cameraName = call.arguments[@"cameraName"]; - NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - enableAudio:[enableAudio boolValue] - dispatchQueue:_dispatchQueue - error:&error]; - - if (error) { - result(getFlutterError(error)); - } else { - if (_camera) { - [_camera close]; - } - int64_t textureId = [_registry registerTexture:cam]; - _camera = cam; - - result(@{ - @"cameraId" : @(textureId), - }); + if ([@"availableCameras" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + NSArray *devices = discoverySession.devices; + NSMutableArray *> *reply = + [[NSMutableArray alloc] initWithCapacity:devices.count]; + for (AVCaptureDevice *device in devices) { + NSString *lensFacing; + switch ([device position]) { + case AVCaptureDevicePositionBack: + lensFacing = @"back"; + break; + case AVCaptureDevicePositionFront: + lensFacing = @"front"; + break; + case AVCaptureDevicePositionUnspecified: + lensFacing = @"external"; + break; } - } else if ([@"startImageStream" isEqualToString:call.method]) { - [_camera startImageStreamWithMessenger:_messenger]; - result(nil); - } else if ([@"stopImageStream" isEqualToString:call.method]) { - [_camera stopImageStream]; - result(nil); + [reply addObject:@{ + @"name" : [device uniqueID], + @"lensFacing" : lensFacing, + @"sensorOrientation" : @90, + }]; + } + result(reply); } else { - NSDictionary *argsMap = call.arguments; - NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; - if ([@"initialize" isEqualToString:call.method]) { - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:cameraId]; - }; - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", - (unsigned long) cameraId] - binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel invokeMethod:@"initialized" arguments:@{ - @"previewWidth" : @(_camera.previewSize.width), - @"previewHeight" : @(_camera.previewSize.height) - }]; - [_camera start]; - result(nil); - } else if ([@"takePicture" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - [_camera captureToFile:result]; - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:cameraId]; - [_camera close]; - _dispatchQueue = nil; - result(nil); - } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { - [_camera setUpCaptureSessionForAudio]; - result(nil); - } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingWithResult:result]; - } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecordingWithResult:result]; - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecordingWithResult:result]; - } else { - result(FlutterMethodNotImplemented); - } + result(FlutterMethodNotImplemented); + } + } else if ([@"create" isEqualToString:call.method]) { + NSString *cameraName = call.arguments[@"cameraName"]; + NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; + NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName + resolutionPreset:resolutionPreset + enableAudio:[enableAudio boolValue] + dispatchQueue:_dispatchQueue + error:&error]; + + if (error) { + result(getFlutterError(error)); + } else { + if (_camera) { + [_camera close]; + } + int64_t textureId = [_registry registerTexture:cam]; + _camera = cam; + + result(@{ + @"cameraId" : @(textureId), + }); + } + } else if ([@"startImageStream" isEqualToString:call.method]) { + [_camera startImageStreamWithMessenger:_messenger]; + result(nil); + } else if ([@"stopImageStream" isEqualToString:call.method]) { + [_camera stopImageStream]; + result(nil); + } else { + NSDictionary *argsMap = call.arguments; + NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; + if ([@"initialize" isEqualToString:call.method]) { + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", + (unsigned long)cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" + arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) + }]; + [_camera start]; + result(nil); + } else if ([@"takePicture" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + [_camera captureToFile:result]; + } else { + result(FlutterMethodNotImplemented); + } + } else if ([@"dispose" isEqualToString:call.method]) { + [_registry unregisterTexture:cameraId]; + [_camera close]; + _dispatchQueue = nil; + result(nil); + } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { + [_camera setUpCaptureSessionForAudio]; + result(nil); + } else if ([@"startVideoRecording" isEqualToString:call.method]) { + [_camera startVideoRecordingWithResult:result]; + } else if ([@"stopVideoRecording" isEqualToString:call.method]) { + [_camera stopVideoRecordingWithResult:result]; + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecordingWithResult:result]; + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecordingWithResult:result]; + } else { + result(FlutterMethodNotImplemented); } + } } @end diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 7b3db21f4b34..32e454e99f96 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -133,8 +133,6 @@ void main() { await cameraController.initialize(); expect(cameraController.value.isInitialized, isTrue); - - }); }); } @@ -142,7 +140,6 @@ void main() { class MockCameraPlatform extends Mock with MockPlatformInterfaceMixin implements CameraPlatform { - @override Future> availableCameras() => Future.value(mockAvailableCameras); From d1f6cd19b6fb26da462d791fc45aa004d65e9689 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 21:23:09 +0100 Subject: [PATCH 40/67] Fixed tests, formatting and analysis warnings --- .../example/integration_test/camera_test.dart | 24 +++------ packages/camera/camera/lib/camera.dart | 43 +++++++++++++-- packages/camera/camera/pubspec.yaml | 4 ++ packages/camera/camera/test/camera_test.dart | 52 +++++-------------- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index 146126963632..c2e73e0f1563 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -5,7 +5,6 @@ import 'dart:ui'; import 'package:camera/camera.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; import 'package:integration_test/integration_test.dart'; @@ -56,12 +55,10 @@ void main() { 'Capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Picture - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg'; - await controller.takePicture(filePath); + final file = await controller.takePicture(); // Load picture - final File fileImage = File(filePath); + final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected @@ -103,14 +100,12 @@ void main() { 'Capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Video - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); // Load video metadata - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); @@ -161,13 +156,10 @@ void main() { await controller.initialize(); await controller.prepareForVideoRecording(); - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - int startPause; int timePaused = 0; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); @@ -187,11 +179,11 @@ void main() { sleep(const Duration(milliseconds: 500)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index fa11c1108061..e313314ac36e 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -273,18 +273,40 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if image streaming or video recording has /// already started. // TODO(bmparr): Add settings for resolution and fps. - Widget buildView() { + Future startImageStream(onLatestImageAvailable onAvailable) async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', - 'buildView() was called on uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', ); } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ); + } + try { - return CameraPlatform.instance.buildView(_cameraId); + await _channel.invokeMethod('startImageStream'); + value = value.copyWith(isStreamingImages: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen( + (dynamic imageData) { + onAvailable(CameraImage.fromPlatformData(imageData)); + }, + ); } /// Stop streaming images from platform camera. @@ -427,6 +449,21 @@ class CameraController extends ValueNotifier { } } + /// Returns a widget showing a live camera preview. + Widget buildView() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'buildView() was called on uninitialized CameraController.', + ); + } + try { + return CameraPlatform.instance.buildView(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 08f549bf241f..c84c417371be 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -5,9 +5,13 @@ description: A Flutter plugin for getting information about and controlling the version: 0.5.8+19 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera +# TODO(mvanbeusekom): Remove when camera_platform_interface package is published. +publish_to: none + dependencies: flutter: sdk: flutter + # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published camera_platform_interface: path: ../camera_platform_interface diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 32e454e99f96..89d0d7cb88ff 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -5,13 +5,10 @@ import 'dart:async'; import 'dart:ui'; import 'package:camera/camera.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:rxdart/rxdart.dart'; get mockAvailableCameras => [ CameraDescription( @@ -26,8 +23,7 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnResolutionChangedEvent => - ResolutionChangedEvent(13, 100, 100, 75, 75); +get mockOnCameraInitializedEvent => CameraInitializedEvent(13, 75, 75); get mockOnCameraClosingEvent => null; @@ -113,7 +109,7 @@ void main() { verify(CameraPlatform.instance.dispose(13)).called(1); }); - test('initialize() returns when disposed', () async { + test('initialize() throws CameraException when disposed', () async { CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -129,10 +125,14 @@ void main() { await cameraController.dispose(); verify(CameraPlatform.instance.dispose(13)).called(1); - expect(cameraController.value.isInitialized, isFalse); - await cameraController.initialize(); - expect(cameraController.value.isInitialized, isTrue); + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'Error description', + 'initialize was called on a disposed CameraController', + ))); }); }); } @@ -145,16 +145,16 @@ class MockCameraPlatform extends Mock Future.value(mockAvailableCameras); @override - Future initializeCamera( - CameraDescription cameraDescription, + Future createCamera( + CameraDescription description, ResolutionPreset resolutionPreset, { bool enableAudio, }) => Future.value(mockInitializeCamera); @override - Stream onResolutionChanged(int cameraId) { - return Stream.value(mockOnResolutionChangedEvent); + Stream onCameraInitialized(int cameraId) { + return Stream.value(mockOnCameraInitializedEvent); } @override @@ -170,35 +170,9 @@ class MockCameraPlatform extends Mock @override Future takePicture(int cameraId) => Future.value(mockTakePicture); - // @override - // Future prepareForVideoRecording() { - // - // } - @override Future startVideoRecording(int cameraId) => Future.value(mockVideoRecordingXFile); - -// @override -// Future stopVideoRecording(int cameraId) { -// -// } - -// @override -// Future pauseVideoRecording(int cameraId) { -// throw UnimplementedError('pauseVideoRecording() is not implemented.'); -// } - -// @override -// Future resumeVideoRecording(int cameraId) { -// throw UnimplementedError('resumeVideoRecording() is not implemented.'); -// } - -// @override -// Widget buildView(int cameraId) { -// throw UnimplementedError('buildView() has not been implemented.'); -// } - } class MockCameraDescription extends CameraDescription { From 6a30ccdbcf76e4209b37833b93b4329c8ecee906 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 09:37:44 +0100 Subject: [PATCH 41/67] Added missing license to Dart files --- packages/camera/camera/test/camera_test.dart | 1 + packages/camera/camera/test/camera_value_test.dart | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 89d0d7cb88ff..66de0d9db5bf 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1,6 +1,7 @@ // Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + import 'dart:async'; import 'dart:ui'; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 4a70f059cb5e..28255eb0a568 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'dart:ui'; import 'package:camera/camera.dart'; From b26d633c96405a31722cf1b82d58ef45dc85dc14 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 10:22:09 +0100 Subject: [PATCH 42/67] Updated CHANGELOG and version --- packages/camera/camera/CHANGELOG.md | 10 ++++++++++ packages/camera/camera/example/lib/main.dart | 2 +- packages/camera/camera/lib/camera.dart | 4 ++-- packages/camera/camera/pubspec.yaml | 2 +- .../test/method_channel_camera_test.dart | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index d1d11c4d8877..4a16530a2a4c 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.6.0 + +As part of implementing federated architecture and making the interface compatible with the web this version contains the following **breaking changes**: + +Method changes in `CameraController`: +- The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; +- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; +- The `stopVideoRecording` method now returns the captured video when it completes; +- Added the `buildPreview` method which replaces the seperate `CameraPreview` widget. + ## 0.5.8+19 * Update Flutter SDK constraint. diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index aa0c05d5eb33..0186d47d10e8 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -131,7 +131,7 @@ class _CameraExampleHomeState extends State } else { return AspectRatio( aspectRatio: controller.value.aspectRatio, - child: controller.buildView(), + child: controller.buildPreview(), ); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index e313314ac36e..b0d7db9e5b02 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -450,7 +450,7 @@ class CameraController extends ValueNotifier { } /// Returns a widget showing a live camera preview. - Widget buildView() { + Widget buildPreview() { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -458,7 +458,7 @@ class CameraController extends ValueNotifier { ); } try { - return CameraPlatform.instance.buildView(_cameraId); + return CameraPlatform.instance.buildPreview(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index c84c417371be..382ac2e975ba 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.8+19 +version: 0.6.0 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera # TODO(mvanbeusekom): Remove when camera_platform_interface package is published. diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 009be3846088..8b36ef62860e 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -394,7 +394,7 @@ void main() { test('Should build a texture widget as view widget', () async { // Act - Widget widget = camera.buildView(cameraId); + Widget widget = camera.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); From 9055fe1e06cfd5a0ab836fc9cb514cbb3c9ec3fe Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 18:24:31 +0100 Subject: [PATCH 43/67] Added additional unit-tests to platform_interface --- .../method_channel/method_channel_camera.dart | 8 +- .../test/method_channel_camera_test.dart | 405 ------------------ 2 files changed, 3 insertions(+), 410 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/test/method_channel_camera_test.dart diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 87b8e44b05b2..bc836b0ac98e 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -29,11 +29,9 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); - Stream _events(int cameraId) => cameraEventStreamController - .stream - .tap((event) => print( - 'Event received, EVENT TYPE: ${event.runtimeType}, STRING: ${event.toString()}')) - .where((event) => event.cameraId == cameraId); + Stream _events(int cameraId) => + cameraEventStreamController.stream + .where((event) => event.cameraId == cameraId); @override Future> availableCameras() async { diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart deleted file mode 100644 index 8b36ef62860e..000000000000 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:async/async.dart'; -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'utils/method_channel_mock.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$MethodChannelCamera', () { - group('Creation, Initialization & Disposal Tests', () { - test('Should send creation data and receive back a camera id', () async { - // Arrange - MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1} - }); - final camera = MethodChannelCamera(); - - // Act - final cameraId = await camera.createCamera( - CameraDescription(name: 'Test'), - ResolutionPreset.high, - ); - - // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'high', - 'enableAudio': null - }, - ), - ]); - expect(cameraId, 1); - }); - - test('Should send initialization data', () async { - // Arrange - MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }); - final camera = MethodChannelCamera(); - final cameraId = await camera.createCamera( - CameraDescription(name: 'Test'), - ResolutionPreset.high, - ); - - // Act - Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); - await initializeFuture; - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - isMethodCall( - 'initialize', - arguments: { - 'cameraId': 1, - }, - ), - ]); - }); - - test('Should send a disposal call on dispose', () async { - // Arrange - MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null, - 'dispose': {'cameraId': 1} - }); - - final camera = MethodChannelCamera(); - final cameraId = await camera.createCamera( - CameraDescription(name: 'Test'), - ResolutionPreset.high, - ); - Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); - await initializeFuture; - - // Act - await camera.dispose(cameraId); - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - anything, - isMethodCall( - 'dispose', - arguments: {'cameraId': 1}, - ), - ]); - }); - }); - - group('Event Tests', () { - MethodChannelCamera camera; - int cameraId; - setUp(() async { - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = MethodChannelCamera(); - cameraId = await camera.createCamera( - CameraDescription(name: 'Test'), - ResolutionPreset.high, - ); - Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); - await initializeFuture; - }); - - test('Should receive initialized event', () async { - // Act - final Stream eventStream = - camera.onCameraInitialized(cameraId); - final streamQueue = StreamQueue(eventStream); - - // Emit test events - final event = CameraInitializedEvent(cameraId, 3840, 2160); - await camera.handleMethodCall( - MethodCall('initialized', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive resolution changes', () async { - // Act - final Stream resolutionStream = - camera.onCameraResolutionChanged(cameraId); - final streamQueue = StreamQueue(resolutionStream); - - // Emit test events - final fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); - final uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); - await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera closing events', () async { - // Act - final Stream eventStream = - camera.onCameraClosing(cameraId); - final streamQueue = StreamQueue(eventStream); - - // Emit test events - final event = CameraClosingEvent(cameraId); - await camera.handleMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera error events', () async { - // Act - final errorStream = camera.onCameraError(cameraId); - final streamQueue = StreamQueue(errorStream); - - // Emit test events - final event = CameraErrorEvent(cameraId, 'Error Description'); - await camera.handleMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleMethodCall( - MethodCall('error', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - }); - - group('Function Tests', () { - MethodChannelCamera camera; - int cameraId; - setUp(() async { - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = MethodChannelCamera(); - cameraId = await camera.createCamera( - CameraDescription(name: 'Test'), - ResolutionPreset.high, - ); - Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); - await initializeFuture; - }); - - test('Should fetch CameraDescription instances for available cameras', - () async { - // Arrange - List> returnData = [ - {'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1}, - {'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2} - ]; - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'availableCameras': returnData}, - ); - - // Act - List cameras = await camera.availableCameras(); - - // Assert - expect(channel.log, [ - isMethodCall('availableCameras', arguments: null), - ]); - expect(cameras.length, returnData.length); - for (int i = 0; i < returnData.length; i++) { - CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name'], - lensDirection: - camera.parseCameraLensDirection(returnData[i]['lensFacing']), - sensorOrientation: returnData[i]['sensorOrientation'], - ); - expect(cameras[i], cameraDescription); - } - }); - - test('Should take a picture and return an XFile instance', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'takePicture': '/test/path.jpg'}); - - // Act - XFile file = await camera.takePicture(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('takePicture', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.jpg'); - }); - - test('Should prepare for video recording', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'prepareForVideoRecording': null}, - ); - - // Act - await camera.prepareForVideoRecording(); - - // Assert - expect(channel.log, [ - isMethodCall('prepareForVideoRecording', arguments: null), - ]); - }); - - test('Should start recording a video', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': null}, - ); - - // Act - await camera.startVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should stop a video recording and return the file', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': '/test/path.mp4'}, - ); - - // Act - XFile file = await camera.stopVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.mp4'); - }); - - test('Should pause a video recording', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'pauseVideoRecording': null}, - ); - - // Act - await camera.pauseVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('pauseVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should resume a video recording', () async { - // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'resumeVideoRecording': null}, - ); - - // Act - await camera.resumeVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('resumeVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should build a texture widget as view widget', () async { - // Act - Widget widget = camera.buildPreview(cameraId); - - // Act - expect(widget is Texture, isTrue); - expect((widget as Texture).textureId, cameraId); - }); - }); - }); -} From e8c2977cf37383c5506d60763db185ec195c626c Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 30 Nov 2020 16:39:12 +0100 Subject: [PATCH 44/67] Added more tests --- packages/camera/camera/test/camera_test.dart | 359 +++++++++++++++++- .../test/utils/method_channel_mock.dart | 38 ++ 2 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 packages/camera/camera/test/utils/method_channel_mock.dart diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 66de0d9db5bf..f8c783fe8ad4 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -6,11 +6,14 @@ import 'dart:async'; import 'dart:ui'; import 'package:camera/camera.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'utils/method_channel_mock.dart'; + get mockAvailableCameras => [ CameraDescription( name: 'camBack', @@ -30,10 +33,12 @@ get mockOnCameraClosingEvent => null; get mockOnCameraErrorEvent => CameraErrorEvent(13, 'closing'); -get mockTakePicture => null; +XFile mockTakePicture = XFile('foo/bar.png'); get mockVideoRecordingXFile => null; +bool mockPlatformException = false; + void main() { WidgetsFlutterBinding.ensureInitialized(); group('camera', () { @@ -135,6 +140,335 @@ void main() { 'initialize was called on a disposed CameraController', ))); }); + + test('initialize() throws $CameraException on $PlatformException ', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + mockPlatformException = true; + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('prepareForVideoRecording() calls $CameraPlatform ', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.prepareForVideoRecording(); + + verify(CameraPlatform.instance.prepareForVideoRecording()).called(1); + }); + + test('takePicture() throws $CameraException when uninitialized ', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ))); + }); + + test('takePicture() throws $CameraException when takePicture is true', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isTakingPicture: true); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ))); + }); + + test('takePicture() returns $XFile', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + XFile xFile = await cameraController.takePicture(); + + expect(xFile.path, mockTakePicture.path); + }); + + test('takePicture() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('startImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ))); + }); + + test('startImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ))); + }); + test( + 'startImageStream() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); + + + + test('startImageStream() calls CameraPlatform', () async { + MethodChannelMock methodChannelMock = + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'startImageStream': {} + }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + + expect(methodChannelMock.log, + [isMethodCall('startImageStream', arguments: null)]); + }); + + test('stopImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'stopImageStream was called on uninitialized CameraController.', + ))); + }); + + test('stopImageStream() throws $CameraException when recording videos', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ))); + }); + + test('stopImageStream() throws $CameraException when not streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); + + + test('stopImageStream() intended behaviour', () async { + MethodChannelMock methodChannelMock = + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'startImageStream': {}, + 'stopImageStream': {} + }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + await cameraController.startImageStream((image) => null); + await cameraController.stopImageStream(); + expect(methodChannelMock.log, + [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); + }); + + test('startVideoRecording() throws $CameraException when uninitialized', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); + }); + test('startVideoRecording() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); + }); + + test( + 'startVideoRecording() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); + }); }); } @@ -151,25 +485,26 @@ class MockCameraPlatform extends Mock ResolutionPreset resolutionPreset, { bool enableAudio, }) => - Future.value(mockInitializeCamera); + mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockInitializeCamera); @override - Stream onCameraInitialized(int cameraId) { - return Stream.value(mockOnCameraInitializedEvent); - } + Stream onCameraInitialized(int cameraId) => + Stream.value(mockOnCameraInitializedEvent); @override - Stream onCameraClosing(int cameraId) { - return Stream.value(mockOnCameraClosingEvent); - } + Stream onCameraClosing(int cameraId) => + Stream.value(mockOnCameraClosingEvent); @override - Stream onCameraError(int cameraId) { - return Stream.value(mockOnCameraErrorEvent); - } + Stream onCameraError(int cameraId) => + Stream.value(mockOnCameraErrorEvent); @override - Future takePicture(int cameraId) => Future.value(mockTakePicture); + Future takePicture(int cameraId) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); @override Future startVideoRecording(int cameraId) => diff --git a/packages/camera/camera/test/utils/method_channel_mock.dart b/packages/camera/camera/test/utils/method_channel_mock.dart new file mode 100644 index 000000000000..cdf393f82b5f --- /dev/null +++ b/packages/camera/camera/test/utils/method_channel_mock.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +class MethodChannelMock { + final Duration delay; + final MethodChannel methodChannel; + final Map methods; + final log = []; + + MethodChannelMock({ + String channelName, + this.delay, + this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} From 086c3ed1664e6f56b59fa11c96e3f668c78fb5af Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 30 Nov 2020 16:44:00 +0100 Subject: [PATCH 45/67] Formatted code --- packages/camera/camera/test/camera_test.dart | 238 +++++++++---------- 1 file changed, 116 insertions(+), 122 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index f8c783fe8ad4..24e0592bed8e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -257,7 +257,7 @@ void main() { expect( cameraController.startImageStream((image) => null), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'startImageStream was called on uninitialized CameraController.', ))); @@ -265,53 +265,50 @@ void main() { test('startImageStream() throws $CameraException when recording videos', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ))); }); test( 'startImageStream() throws $CameraException when already streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ))); - }); - + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); test('startImageStream() calls CameraPlatform', () async { - MethodChannelMock methodChannelMock = - MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { - 'startImageStream': {} - }); + MethodChannelMock methodChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}}); CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -337,13 +334,14 @@ void main() { expect( cameraController.stopImageStream(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'stopImageStream was called on uninitialized CameraController.', ))); }); - test('stopImageStream() throws $CameraException when recording videos', () async { + test('stopImageStream() throws $CameraException when recording videos', + () async { CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -358,7 +356,7 @@ void main() { expect( cameraController.stopImageStream(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'A video recording is already started.', 'stopImageStream was called while a video is being recorded.', ))); @@ -366,30 +364,27 @@ void main() { test('stopImageStream() throws $CameraException when not streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - expect( - cameraController.stopImageStream(), - throwsA(isA().having( - (error) => error.description, - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ))); - }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); test('stopImageStream() intended behaviour', () async { - MethodChannelMock methodChannelMock = - MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { - 'startImageStream': {}, - 'stopImageStream': {} - }); + MethodChannelMock methodChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}, 'stopImageStream': {}}); CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -399,75 +394,74 @@ void main() { await cameraController.initialize(); await cameraController.startImageStream((image) => null); await cameraController.stopImageStream(); - expect(methodChannelMock.log, - [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null) - ]); + expect(methodChannelMock.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); }); test('startVideoRecording() throws $CameraException when uninitialized', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); }); test('startVideoRecording() throws $CameraException when recording videos', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); }); test( 'startVideoRecording() throws $CameraException when already streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); }); }); } From 78fac3afa3b58b30f2aaa7b402c33ba0b099be14 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Dec 2020 14:37:11 +0100 Subject: [PATCH 46/67] Re-added the CameraPreview widget --- packages/camera/camera/example/lib/main.dart | 2 +- packages/camera/camera/lib/camera.dart | 474 +----------------- .../camera/lib/src/camera_controller.dart | 470 +++++++++++++++++ .../camera/lib/{ => src}/camera_image.dart | 2 +- .../camera/camera/lib/src/camera_preview.dart | 21 + 5 files changed, 500 insertions(+), 469 deletions(-) create mode 100644 packages/camera/camera/lib/src/camera_controller.dart rename packages/camera/camera/lib/{ => src}/camera_image.dart (99%) create mode 100644 packages/camera/camera/lib/src/camera_preview.dart diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 0186d47d10e8..e1edc1b06386 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -131,7 +131,7 @@ class _CameraExampleHomeState extends State } else { return AspectRatio( aspectRatio: controller.value.aspectRatio, - child: controller.buildPreview(), + child: CameraPreview(controller), ); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index b0d7db9e5b02..bc513cef54a8 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -2,479 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +library camera; + import 'dart:async'; import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:pedantic/pedantic.dart'; -export 'package:camera_platform_interface/camera_platform_interface.dart'; - -part 'camera_image.dart'; - -final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); - -/// Signature for a callback receiving the a camera image. -/// -/// This is used by [CameraController.startImageStream]. -// ignore: inference_failure_on_function_return_type -typedef onLatestImageAvailable = Function(CameraImage image); - -/// Completes with a list of available cameras. -/// -/// May throw a [CameraException]. -Future> availableCameras() async { - return CameraPlatform.instance.availableCameras(); -} - -/// The state of a [CameraController]. -class CameraValue { - /// Creates a new camera controller state. - const CameraValue({ - this.isInitialized, - this.errorDescription, - this.previewSize, - this.isRecordingVideo, - this.isTakingPicture, - this.isStreamingImages, - bool isRecordingPaused, - }) : _isRecordingPaused = isRecordingPaused; - - /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized() - : this( - isInitialized: false, - isRecordingVideo: false, - isTakingPicture: false, - isStreamingImages: false, - isRecordingPaused: false, - ); - - /// True after [CameraController.initialize] has completed successfully. - final bool isInitialized; - - /// True when a picture capture request has been sent but as not yet returned. - final bool isTakingPicture; - - /// True when the camera is recording (not the same as previewing). - final bool isRecordingVideo; - - /// True when images from the camera are being streamed. - final bool isStreamingImages; - - final bool _isRecordingPaused; - - /// True when camera [isRecordingVideo] and recording is paused. - bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; - - /// Description of an error state. - /// - /// This is null while the controller is not in an error state. - /// When [hasError] is true this contains the error description. - final String errorDescription; - - /// The size of the preview in pixels. - /// - /// Is `null` until [isInitialized] is `true`. - final Size previewSize; - - /// Convenience getter for `previewSize.height / previewSize.width`. - /// - /// Can only be called when [initialize] is done. - double get aspectRatio => previewSize.height / previewSize.width; - - /// Whether the controller is in an error state. - /// - /// When true [errorDescription] describes the error. - bool get hasError => errorDescription != null; - - /// Creates a modified copy of the object. - /// - /// Explicitly specified fields get the specified value, all other fields get - /// the same value of the current object. - CameraValue copyWith({ - bool isInitialized, - bool isRecordingVideo, - bool isTakingPicture, - bool isStreamingImages, - String errorDescription, - Size previewSize, - bool isRecordingPaused, - }) { - return CameraValue( - isInitialized: isInitialized ?? this.isInitialized, - errorDescription: errorDescription, - previewSize: previewSize ?? this.previewSize, - isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, - isTakingPicture: isTakingPicture ?? this.isTakingPicture, - isStreamingImages: isStreamingImages ?? this.isStreamingImages, - isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, - ); - } - - @override - String toString() { - return '$runtimeType(' - 'isRecordingVideo: $isRecordingVideo, ' - 'isInitialized: $isInitialized, ' - 'errorDescription: $errorDescription, ' - 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages)'; - } -} - -/// Controls a device camera. -/// -/// Use [availableCameras] to get a list of available cameras. -/// -/// Before using a [CameraController] a call to [initialize] must complete. -/// -/// To show the camera preview on the screen use a [CameraPreview] widget. -class CameraController extends ValueNotifier { - /// Creates a new camera controller in an uninitialized state. - CameraController( - this.description, - this.resolutionPreset, { - this.enableAudio = true, - }) : super(const CameraValue.uninitialized()); - - /// The properties of the camera device controlled by this controller. - final CameraDescription description; - /// The resolution this controller is targeting. - /// - /// This resolution preset is not guaranteed to be available on the device, - /// if unavailable a lower resolution will be used. - /// - /// See also: [ResolutionPreset]. - final ResolutionPreset resolutionPreset; - - /// Whether to include audio when recording a video. - final bool enableAudio; - - int _cameraId; - bool _isDisposed = false; - StreamSubscription _imageStreamSubscription; - Completer _creatingCompleter; - - /// Checks whether [CameraController.dispose] has completed successfully. - /// - /// This is a no-op when asserts are disabled. - void debugCheckIsDisposed() { - assert(_isDisposed); - } - - /// Initializes the camera on the device. - /// - /// Throws a [CameraException] if the initialization fails. - Future initialize() async { - if (_isDisposed) { - throw CameraException( - 'Disposed CameraController', - 'initialize was called on a disposed CameraController', - ); - } - try { - _creatingCompleter = Completer(); - Completer _resolutionCompleter = Completer(); - - _cameraId = await CameraPlatform.instance.createCamera( - description, - resolutionPreset, - enableAudio: enableAudio, - ); - - unawaited( - CameraPlatform.instance - .onCameraInitialized(_cameraId) - .map((event) { - return Size( - event.previewWidth, - event.previewHeight, - ); - }) - .first - .then((previewSize) => _resolutionCompleter.complete(previewSize)), - ); - - await CameraPlatform.instance.initializeCamera(_cameraId); - - value = value.copyWith( - isInitialized: true, - previewSize: await _resolutionCompleter.future, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - _creatingCompleter.complete(); - return _creatingCompleter.future; - } - - /// Prepare the capture session for video recording. - /// - /// Use of this method is optional, but it may be called for performance - /// reasons on iOS. - /// - /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. - /// If video recording is intended, calling this early eliminates this delay - /// that would otherwise be experienced when video recording is started. - /// This operation is a no-op on Android. - /// - /// Throws a [CameraException] if the prepare fails. - Future prepareForVideoRecording() async { - await CameraPlatform.instance.prepareForVideoRecording(); - } - - /// Captures an image and saves it to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as this function returns. - /// - /// Throws a [CameraException] if the capture fails. - Future takePicture() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController.', - 'takePicture was called on uninitialized CameraController', - ); - } - if (value.isTakingPicture) { - throw CameraException( - 'Previous capture has not returned yet.', - 'takePicture was called before the previous capture returned.', - ); - } - try { - value = value.copyWith(isTakingPicture: true); - XFile file = await CameraPlatform.instance.takePicture(_cameraId); - value = value.copyWith(isTakingPicture: false); - return file; - } on PlatformException catch (e) { - value = value.copyWith(isTakingPicture: false); - throw CameraException(e.code, e.message); - } - } - - /// Start streaming images from platform camera. - /// - /// Settings for capturing images on iOS and Android is set to always use the - /// latest image available from the camera and will drop all other images. - /// - /// When running continuously with [CameraPreview] widget, this function runs - /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can - /// have significant frame rate drops for [CameraPreview] on lower end - /// devices. - /// - /// Throws a [CameraException] if image streaming or video recording has - /// already started. - // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startImageStream was called on uninitialized CameraController.', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ); - } - - try { - await _channel.invokeMethod('startImageStream'); - value = value.copyWith(isStreamingImages: true); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( - (dynamic imageData) { - onAvailable(CameraImage.fromPlatformData(imageData)); - }, - ); - } - - /// Stop streaming images from platform camera. - /// - /// Throws a [CameraException] if image streaming was not started or video - /// recording was started. - Future stopImageStream() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopImageStream was called on uninitialized CameraController.', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', - ); - } - if (!value.isStreamingImages) { - throw CameraException( - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ); - } - - try { - value = value.copyWith(isStreamingImages: false); - await _channel.invokeMethod('stopImageStream'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - - await _imageStreamSubscription.cancel(); - _imageStreamSubscription = null; - } - - /// Start a video recording. - /// - /// The video is returned as a [XFile] after calling [stopVideoRecording]. - /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ); - } - - try { - await CameraPlatform.instance.startVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Stops the video recording and returns the file where it was saved. - /// - /// Throws a [CameraException] if the capture failed. - Future stopVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'stopVideoRecording was called when no video is recording.', - ); - } - try { - XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); - return file; - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Pause video recording. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future pauseVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'pauseVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'pauseVideoRecording was called when no video is recording.', - ); - } - try { - await CameraPlatform.instance.pauseVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: true); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Resume video recording after pausing. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future resumeVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'resumeVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'resumeVideoRecording was called when no video is recording.', - ); - } - try { - await CameraPlatform.instance.resumeVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: false); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Returns a widget showing a live camera preview. - Widget buildPreview() { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'buildView() was called on uninitialized CameraController.', - ); - } - try { - return CameraPlatform.instance.buildPreview(_cameraId); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } +export 'package:camera_platform_interface/camera_platform_interface.dart'; - /// Releases the resources of this camera. - @override - Future dispose() async { - if (_isDisposed) { - return; - } - _isDisposed = true; - super.dispose(); - if (_creatingCompleter != null) { - await _creatingCompleter.future; - await CameraPlatform.instance.dispose(_cameraId); - } - } -} +part 'src/camera_controller.dart'; +part 'src/camera_image.dart'; +part 'src/camera_preview.dart'; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart new file mode 100644 index 000000000000..d41fe512ae45 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -0,0 +1,470 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of camera; + +final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraController.startImageStream]. +// ignore: inference_failure_on_function_return_type +typedef onLatestImageAvailable = Function(CameraImage image); + +/// Completes with a list of available cameras. +/// +/// May throw a [CameraException]. +Future> availableCameras() async { + return CameraPlatform.instance.availableCameras(); +} + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + this.isInitialized, + this.errorDescription, + this.previewSize, + this.isRecordingVideo, + this.isTakingPicture, + this.isStreamingImages, + bool isRecordingPaused, + }) : _isRecordingPaused = isRecordingPaused; + + /// Creates a new camera controller state for an uninitialized controller. + const CameraValue.uninitialized() + : this( + isInitialized: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + isRecordingPaused: false, + ); + + /// True after [CameraController.initialize] has completed successfully. + final bool isInitialized; + + /// True when a picture capture request has been sent but as not yet returned. + final bool isTakingPicture; + + /// True when the camera is recording (not the same as previewing). + final bool isRecordingVideo; + + /// True when images from the camera are being streamed. + final bool isStreamingImages; + + final bool _isRecordingPaused; + + /// True when camera [isRecordingVideo] and recording is paused. + bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; + + /// Description of an error state. + /// + /// This is null while the controller is not in an error state. + /// When [hasError] is true this contains the error description. + final String errorDescription; + + /// The size of the preview in pixels. + /// + /// Is `null` until [isInitialized] is `true`. + final Size previewSize; + + /// Convenience getter for `previewSize.height / previewSize.width`. + /// + /// Can only be called when [initialize] is done. + double get aspectRatio => previewSize.height / previewSize.width; + + /// Whether the controller is in an error state. + /// + /// When true [errorDescription] describes the error. + bool get hasError => errorDescription != null; + + /// Creates a modified copy of the object. + /// + /// Explicitly specified fields get the specified value, all other fields get + /// the same value of the current object. + CameraValue copyWith({ + bool isInitialized, + bool isRecordingVideo, + bool isTakingPicture, + bool isStreamingImages, + String errorDescription, + Size previewSize, + bool isRecordingPaused, + }) { + return CameraValue( + isInitialized: isInitialized ?? this.isInitialized, + errorDescription: errorDescription, + previewSize: previewSize ?? this.previewSize, + isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, + isTakingPicture: isTakingPicture ?? this.isTakingPicture, + isStreamingImages: isStreamingImages ?? this.isStreamingImages, + isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, + ); + } + + @override + String toString() { + return '$runtimeType(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'errorDescription: $errorDescription, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages)'; + } +} + +/// Controls a device camera. +/// +/// Use [availableCameras] to get a list of available cameras. +/// +/// Before using a [CameraController] a call to [initialize] must complete. +/// +/// To show the camera preview on the screen use a [CameraPreview] widget. +class CameraController extends ValueNotifier { + /// Creates a new camera controller in an uninitialized state. + CameraController( + this.description, + this.resolutionPreset, { + this.enableAudio = true, + }) : super(const CameraValue.uninitialized()); + + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + + /// The resolution this controller is targeting. + /// + /// This resolution preset is not guaranteed to be available on the device, + /// if unavailable a lower resolution will be used. + /// + /// See also: [ResolutionPreset]. + final ResolutionPreset resolutionPreset; + + /// Whether to include audio when recording a video. + final bool enableAudio; + + int _cameraId; + bool _isDisposed = false; + StreamSubscription _imageStreamSubscription; + Completer _creatingCompleter; + + /// Checks whether [CameraController.dispose] has completed successfully. + /// + /// This is a no-op when asserts are disabled. + void debugCheckIsDisposed() { + assert(_isDisposed); + } + + /// Initializes the camera on the device. + /// + /// Throws a [CameraException] if the initialization fails. + Future initialize() async { + if (_isDisposed) { + throw CameraException( + 'Disposed CameraController', + 'initialize was called on a disposed CameraController', + ); + } + try { + _creatingCompleter = Completer(); + Completer _resolutionCompleter = Completer(); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + unawaited( + CameraPlatform.instance + .onCameraInitialized(_cameraId) + .map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }) + .first + .then((previewSize) => _resolutionCompleter.complete(previewSize)), + ); + + await CameraPlatform.instance.initializeCamera(_cameraId); + + value = value.copyWith( + isInitialized: true, + previewSize: await _resolutionCompleter.future, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + _creatingCompleter.complete(); + return _creatingCompleter.future; + } + + /// Prepare the capture session for video recording. + /// + /// Use of this method is optional, but it may be called for performance + /// reasons on iOS. + /// + /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. + /// If video recording is intended, calling this early eliminates this delay + /// that would otherwise be experienced when video recording is started. + /// This operation is a no-op on Android. + /// + /// Throws a [CameraException] if the prepare fails. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Captures an image and saves it to [path]. + /// + /// A path can for example be obtained using + /// [path_provider](https://pub.dartlang.org/packages/path_provider). + /// + /// If a file already exists at the provided path an error will be thrown. + /// The file can be read as this function returns. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ); + } + if (value.isTakingPicture) { + throw CameraException( + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ); + } + try { + value = value.copyWith(isTakingPicture: true); + XFile file = await CameraPlatform.instance.takePicture(_cameraId); + value = value.copyWith(isTakingPicture: false); + return file; + } on PlatformException catch (e) { + value = value.copyWith(isTakingPicture: false); + throw CameraException(e.code, e.message); + } + } + + /// Start streaming images from platform camera. + /// + /// Settings for capturing images on iOS and Android is set to always use the + /// latest image available from the camera and will drop all other images. + /// + /// When running continuously with [CameraPreview] widget, this function runs + /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can + /// have significant frame rate drops for [CameraPreview] on lower end + /// devices. + /// + /// Throws a [CameraException] if image streaming or video recording has + /// already started. + // TODO(bmparr): Add settings for resolution and fps. + Future startImageStream(onLatestImageAvailable onAvailable) async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ); + } + + try { + await _channel.invokeMethod('startImageStream'); + value = value.copyWith(isStreamingImages: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen( + (dynamic imageData) { + onAvailable(CameraImage.fromPlatformData(imageData)); + }, + ); + } + + /// Stop streaming images from platform camera. + /// + /// Throws a [CameraException] if image streaming was not started or video + /// recording was started. + Future stopImageStream() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ); + } + if (!value.isStreamingImages) { + throw CameraException( + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ); + } + + try { + value = value.copyWith(isStreamingImages: false); + await _channel.invokeMethod('stopImageStream'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + + await _imageStreamSubscription.cancel(); + _imageStreamSubscription = null; + } + + /// Start a video recording. + /// + /// The video is returned as a [XFile] after calling [stopVideoRecording]. + /// Throws a [CameraException] if the capture fails. + Future startVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ); + } + + try { + await CameraPlatform.instance.startVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'stopVideoRecording was called when no video is recording.', + ); + } + try { + XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: false); + return file; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Pause video recording. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future pauseVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'pauseVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'pauseVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resume video recording after pausing. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future resumeVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'resumeVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'resumeVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Returns a widget showing a live camera preview. + Widget buildPreview() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'buildView() was called on uninitialized CameraController.', + ); + } + try { + return CameraPlatform.instance.buildPreview(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _isDisposed = true; + super.dispose(); + if (_creatingCompleter != null) { + await _creatingCompleter.future; + await CameraPlatform.instance.dispose(_cameraId); + } + } +} diff --git a/packages/camera/camera/lib/camera_image.dart b/packages/camera/camera/lib/src/camera_image.dart similarity index 99% rename from packages/camera/camera/lib/camera_image.dart rename to packages/camera/camera/lib/src/camera_image.dart index 0b84b19be6e8..ebeb3e1c4bdd 100644 --- a/packages/camera/camera/lib/camera_image.dart +++ b/packages/camera/camera/lib/src/camera_image.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of 'camera.dart'; +part of camera; /// A single color plane of image data. /// diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart new file mode 100644 index 000000000000..8a79c7ee8c50 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -0,0 +1,21 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of camera; + +/// A widget showing a live camera preview. +class CameraPreview extends StatelessWidget { + /// Creates a preview widget for the given camera controller. + const CameraPreview(this.controller); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + @override + Widget build(BuildContext context) { + return controller.value.isInitialized + ? Texture(textureId: controller._cameraId) + : Container(); + } +} From ca13bd5867a682bfbf4371c881a5fbf781850c9a Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 11:39:00 +0100 Subject: [PATCH 47/67] Use import/export instead of part implementation --- packages/camera/camera/lib/camera.dart | 17 ++++------------- .../camera/lib/src/camera_controller.dart | 12 +++++++++++- .../camera/camera/lib/src/camera_image.dart | 5 ++++- .../camera/camera/lib/src/camera_preview.dart | 6 ++++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index bc513cef54a8..bba560470138 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -2,19 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library camera; - -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:pedantic/pedantic.dart'; +export 'src/camera_controller.dart'; +export 'src/camera_image.dart'; +export 'src/camera_preview.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; -part 'src/camera_controller.dart'; -part 'src/camera_image.dart'; -part 'src/camera_preview.dart'; + diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d41fe512ae45..ad8a527c63eb 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -2,7 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'dart:async'; + +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pedantic/pedantic.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); @@ -156,6 +163,9 @@ class CameraController extends ValueNotifier { assert(_isDisposed); } + /// The camera identifier with which the controller is associated. + int get cameraId => _cameraId; + /// Initializes the camera on the device. /// /// Throws a [CameraException] if the initialization fails. diff --git a/packages/camera/camera/lib/src/camera_image.dart b/packages/camera/camera/lib/src/camera_image.dart index ebeb3e1c4bdd..ca8115eb758d 100644 --- a/packages/camera/camera/lib/src/camera_image.dart +++ b/packages/camera/camera/lib/src/camera_image.dart @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// A single color plane of image data. /// diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart index 8a79c7ee8c50..bf7862eb9151 100644 --- a/packages/camera/camera/lib/src/camera_preview.dart +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/material.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { @@ -15,7 +17,7 @@ class CameraPreview extends StatelessWidget { @override Widget build(BuildContext context) { return controller.value.isInitialized - ? Texture(textureId: controller._cameraId) + ? CameraPlatform.instance.buildPreview(controller.cameraId) : Container(); } } From 6fcd5e739257eecb5c127e79739accdec5e0ade3 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 13:08:44 +0100 Subject: [PATCH 48/67] fixed formatting --- packages/camera/camera/lib/camera.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index bba560470138..7135764d2eb5 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -7,5 +7,3 @@ export 'src/camera_image.dart'; export 'src/camera_preview.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; - - From b442759a7f6e652099f695c3213ad036b1d38126 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 13:47:53 +0100 Subject: [PATCH 49/67] Resolved additional feedback --- packages/camera/camera/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 4a16530a2a4c..cb8dfedd1d9a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -6,7 +6,7 @@ Method changes in `CameraController`: - The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; - The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; - The `stopVideoRecording` method now returns the captured video when it completes; -- Added the `buildPreview` method which replaces the seperate `CameraPreview` widget. +- Added the `buildPreview` method which is now used to implement the CameraPreview widget. ## 0.5.8+19 From def916f0db069fd2ede73a7cbce312b0b5dec144 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 4 Dec 2020 09:20:33 +0100 Subject: [PATCH 50/67] Update dependency to git repo --- packages/camera/camera/pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 382ac2e975ba..2afcccb1f1d7 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -13,7 +13,10 @@ dependencies: sdk: flutter # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published camera_platform_interface: - path: ../camera_platform_interface + git: + url: https://github.com/flutter/plugins.git + path: packages/camera/camera_platform_interface + ref: master dev_dependencies: path_provider: ^0.5.0 From 1d1365a736f64bac6b88b4a66e82d6cb44cdea55 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 8 Dec 2020 14:02:19 +0100 Subject: [PATCH 51/67] Depend on pub.dev for camera_platform_interface --- packages/camera/camera/pubspec.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 2afcccb1f1d7..abfce0f8a313 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -12,11 +12,7 @@ dependencies: flutter: sdk: flutter # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published - camera_platform_interface: - git: - url: https://github.com/flutter/plugins.git - path: packages/camera/camera_platform_interface - ref: master + camera_platform_interface: ^1.0.0 dev_dependencies: path_provider: ^0.5.0 From fb14d0cfc2706c196eadf19e51a44c31e5768f52 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 8 Dec 2020 14:05:09 +0100 Subject: [PATCH 52/67] Fix JAVA formatting --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 5ac1dac805bb..2a4f1f5ff008 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -4,7 +4,6 @@ import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.ImageFormat; @@ -17,16 +16,12 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.params.OutputConfiguration; -import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -44,7 +39,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Executors; public class Camera { private final SurfaceTextureEntry flutterTexture; From 69fa625479a1d2ae794aadc3ebb95a87090bcaf1 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 9 Dec 2020 09:12:17 +0100 Subject: [PATCH 53/67] Fix changelog --- packages/camera/camera_platform_interface/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 2d26f8bcca91..500e8d67a6e4 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -13,4 +13,3 @@ ## 1.0.0 - Initial open-source release - First suggestion for camera platform interface From 5bda95c074426d0170e6a165103604798218308e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 9 Dec 2020 11:58:37 +0100 Subject: [PATCH 54/67] Make sure camera package can be published --- packages/camera/camera/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index abfce0f8a313..d97699169f16 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -5,13 +5,9 @@ description: A Flutter plugin for getting information about and controlling the version: 0.6.0 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera -# TODO(mvanbeusekom): Remove when camera_platform_interface package is published. -publish_to: none - dependencies: flutter: sdk: flutter - # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published camera_platform_interface: ^1.0.0 dev_dependencies: From a063f88b35baf24e198022ff297483d4b891a33d Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 9 Dec 2020 16:44:27 +0100 Subject: [PATCH 55/67] Assert when stream methods are called from wrong platform --- .../camera/camera/lib/src/camera_controller.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index ad8a527c63eb..f1cc97395270 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:camera/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; @@ -272,8 +273,14 @@ class CameraController extends ValueNotifier { /// /// Throws a [CameraException] if image streaming or video recording has /// already started. + /// + /// The `startImageStream` method is only available on Android and iOS (other + /// platforms won't be supported in current setup). + /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { + assert(!Platform.isIOS && !Platform.isAndroid); + if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -313,7 +320,12 @@ class CameraController extends ValueNotifier { /// /// Throws a [CameraException] if image streaming was not started or video /// recording was started. + /// + /// The `stopImageStream` method is only available on Android and iOS (other + /// platforms won't be supported in current setup). Future stopImageStream() async { + assert(!Platform.isIOS && !Platform.isAndroid); + if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', From fee08baa5e4da9b7dbbd85b2e21689afc1b379e7 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 9 Dec 2020 18:34:29 -0800 Subject: [PATCH 56/67] Add dev_dependency on plugin_platform_interface package, required by tests. --- packages/camera/camera/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index d97699169f16..64d5bba61159 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -19,6 +19,7 @@ dev_dependencies: sdk: flutter pedantic: ^1.8.0 mockito: ^4.1.3 + plugin_platform_interface: ^1.0.3 flutter: plugin: From ed92b75a912ed9c2ed542af8fd944b4916314afd Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 9 Dec 2020 18:35:58 -0800 Subject: [PATCH 57/67] Remove pedantic requirement from initialize() method. Remove unnecessary completers. --- .../camera/lib/src/camera_controller.dart | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index f1cc97395270..dac78a53086a 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -10,7 +10,6 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:pedantic/pedantic.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); @@ -155,7 +154,7 @@ class CameraController extends ValueNotifier { int _cameraId; bool _isDisposed = false; StreamSubscription _imageStreamSubscription; - Completer _creatingCompleter; + FutureOr _initCalled; /// Checks whether [CameraController.dispose] has completed successfully. /// @@ -178,39 +177,31 @@ class CameraController extends ValueNotifier { ); } try { - _creatingCompleter = Completer(); - Completer _resolutionCompleter = Completer(); - _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); - unawaited( - CameraPlatform.instance - .onCameraInitialized(_cameraId) - .map((event) { - return Size( - event.previewWidth, - event.previewHeight, - ); - }) - .first - .then((previewSize) => _resolutionCompleter.complete(previewSize)), - ); + final previewSize = + CameraPlatform.instance.onCameraInitialized(_cameraId).map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }).first; await CameraPlatform.instance.initializeCamera(_cameraId); value = value.copyWith( isInitialized: true, - previewSize: await _resolutionCompleter.future, + previewSize: await previewSize, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - _creatingCompleter.complete(); - return _creatingCompleter.future; + + _initCalled = true; } /// Prepare the capture session for video recording. @@ -484,8 +475,8 @@ class CameraController extends ValueNotifier { } _isDisposed = true; super.dispose(); - if (_creatingCompleter != null) { - await _creatingCompleter.future; + if (_initCalled != null) { + await _initCalled; await CameraPlatform.instance.dispose(_cameraId); } } From 844d32111a317e27a8ed87f0fcd2155f32af644d Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 10 Dec 2020 23:04:06 +0100 Subject: [PATCH 58/67] Remove dependency on dart:io --- packages/camera/camera/lib/src/camera_controller.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index dac78a53086a..d62a2a5013a1 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:camera/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; @@ -270,7 +269,8 @@ class CameraController extends ValueNotifier { /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { - assert(!Platform.isIOS && !Platform.isAndroid); + assert(defaultTargetPlatform != TargetPlatform.android && + defaultTargetPlatform != TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -315,7 +315,8 @@ class CameraController extends ValueNotifier { /// The `stopImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). Future stopImageStream() async { - assert(!Platform.isIOS && !Platform.isAndroid); + assert(defaultTargetPlatform != TargetPlatform.android && + defaultTargetPlatform != TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( From 381450d3fb9d980b4bb9156544f5cd83f17aa696 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 11 Dec 2020 10:18:34 +0100 Subject: [PATCH 59/67] Restrict exposed types from platform interface --- packages/camera/camera/example/lib/main.dart | 1 + packages/camera/camera/lib/camera.dart | 8 +++++++- packages/camera/camera/test/camera_test.dart | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index e1edc1b06386..d4efb58d4e9b 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -11,6 +11,7 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; + class CameraExampleHome extends StatefulWidget { @override _CameraExampleHomeState createState() { diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 7135764d2eb5..6c6d90b9bcee 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -6,4 +6,10 @@ export 'src/camera_controller.dart'; export 'src/camera_image.dart'; export 'src/camera_preview.dart'; -export 'package:camera_platform_interface/camera_platform_interface.dart'; +export 'package:camera_platform_interface/camera_platform_interface.dart' + show + CameraDescription, + CameraException, + CameraLensDirection, + ResolutionPreset, + XFile; diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 24e0592bed8e..45b8f046ff1e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; From bfc6b92d9d051b0a15b9b37a92bd2a9ff23243b0 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 11 Dec 2020 11:24:03 +0100 Subject: [PATCH 60/67] Moved test for image stream in separate file --- .../camera/lib/src/camera_controller.dart | 8 +- .../camera/test/camera_image_stream_test.dart | 187 ++++++++++++++++++ packages/camera/camera/test/camera_test.dart | 156 +-------------- 3 files changed, 192 insertions(+), 159 deletions(-) create mode 100644 packages/camera/camera/test/camera_image_stream_test.dart diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d62a2a5013a1..fcf00245ce7f 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -269,8 +269,8 @@ class CameraController extends ValueNotifier { /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { - assert(defaultTargetPlatform != TargetPlatform.android && - defaultTargetPlatform != TargetPlatform.iOS); + assert(defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -315,8 +315,8 @@ class CameraController extends ValueNotifier { /// The `stopImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). Future stopImageStream() async { - assert(defaultTargetPlatform != TargetPlatform.android && - defaultTargetPlatform != TargetPlatform.iOS); + assert(defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( diff --git a/packages/camera/camera/test/camera_image_stream_test.dart b/packages/camera/camera/test/camera_image_stream_test.dart new file mode 100644 index 000000000000..be7047f2220f --- /dev/null +++ b/packages/camera/camera/test/camera_image_stream_test.dart @@ -0,0 +1,187 @@ +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'camera_test.dart'; +import 'utils/method_channel_mock.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + CameraPlatform.instance = MockCameraPlatform(); + }); + + test('startImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ))); + }); + + test('startImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ))); + }); + test( + 'startImageStream() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); + + test('startImageStream() calls CameraPlatform', () async { + MethodChannelMock cameraChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}}); + MethodChannelMock streamChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera/imageStream', + methods: {'listen': {}}); + + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + + expect(cameraChannelMock.log, + [isMethodCall('startImageStream', arguments: null)]); + expect(streamChannelMock.log, + [isMethodCall('listen', arguments: null)]); + }); + + test('stopImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'stopImageStream was called on uninitialized CameraController.', + ))); + }); + + test('stopImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ))); + }); + + test('stopImageStream() throws $CameraException when not streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); + + test('stopImageStream() intended behaviour', () async { + MethodChannelMock cameraChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}, 'stopImageStream': {}}); + MethodChannelMock streamChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera/imageStream', + methods: {'listen': {}, 'cancel': {}}); + + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + await cameraController.startImageStream((image) => null); + await cameraController.stopImageStream(); + + expect(cameraChannelMock.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); + + expect(streamChannelMock.log, [ + isMethodCall('listen', arguments: null), + isMethodCall('cancel', arguments: null) + ]); + }); +} diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 45b8f046ff1e..71fdae3f4615 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -13,8 +13,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'utils/method_channel_mock.dart'; - get mockAvailableCameras => [ CameraDescription( name: 'camBack', @@ -42,6 +40,7 @@ bool mockPlatformException = false; void main() { WidgetsFlutterBinding.ensureInitialized(); + group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { @@ -248,159 +247,6 @@ void main() { mockPlatformException = false; }); - test('startImageStream() throws $CameraException when uninitialized', () { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController.', - 'startImageStream was called on uninitialized CameraController.', - ))); - }); - - test('startImageStream() throws $CameraException when recording videos', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ))); - }); - test( - 'startImageStream() throws $CameraException when already streaming images', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ))); - }); - - test('startImageStream() calls CameraPlatform', () async { - MethodChannelMock methodChannelMock = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startImageStream': {}}); - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - await cameraController.startImageStream((image) => null); - - expect(methodChannelMock.log, - [isMethodCall('startImageStream', arguments: null)]); - }); - - test('stopImageStream() throws $CameraException when uninitialized', () { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.stopImageStream(), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController.', - 'stopImageStream was called on uninitialized CameraController.', - ))); - }); - - test('stopImageStream() throws $CameraException when recording videos', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - await cameraController.startImageStream((image) => null); - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - expect( - cameraController.stopImageStream(), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', - ))); - }); - - test('stopImageStream() throws $CameraException when not streaming images', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - expect( - cameraController.stopImageStream(), - throwsA(isA().having( - (error) => error.description, - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ))); - }); - - test('stopImageStream() intended behaviour', () async { - MethodChannelMock methodChannelMock = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startImageStream': {}, 'stopImageStream': {}}); - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - await cameraController.startImageStream((image) => null); - await cameraController.stopImageStream(); - expect(methodChannelMock.log, [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null) - ]); - }); - test('startVideoRecording() throws $CameraException when uninitialized', () async { CameraController cameraController = CameraController( From a0b4dfa34fb09ffb3d9e09c06a273bb787e11fa9 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 11 Dec 2020 11:32:14 +0100 Subject: [PATCH 61/67] Fixed formatting issue --- packages/camera/camera/example/lib/main.dart | 1 - packages/camera/camera/test/camera_test.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index d4efb58d4e9b..e1edc1b06386 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -11,7 +11,6 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; - class CameraExampleHome extends StatefulWidget { @override _CameraExampleHomeState createState() { diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 71fdae3f4615..b129849cd141 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -40,7 +40,7 @@ bool mockPlatformException = false; void main() { WidgetsFlutterBinding.ensureInitialized(); - + group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { From ca6052a81e5c0774e4122c6126051f819fb15a6a Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 11 Dec 2020 16:56:28 +0100 Subject: [PATCH 62/67] Fix deprecation warning --- .../io/flutter/plugins/camera/Camera.java | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 2a4f1f5ff008..39d00ea2962c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -4,6 +4,7 @@ import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.ImageFormat; @@ -16,12 +17,16 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -39,6 +44,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; public class Camera { private final SurfaceTextureEntry flutterTexture; @@ -325,12 +331,42 @@ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession } }; - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); // Start the session - cameraDevice.createCaptureSession(surfaceList, callback, null); + if (VERSION.SDK_INT >= VERSION_CODES.P) { + // Collect all surfaces we want to render to. + List configs = new ArrayList<>(); + configs.add(new OutputConfiguration(flutterSurface)); + for (Surface surface : remainingSurfaces) { + configs.add(new OutputConfiguration(surface)); + } + createCaptureSessionWithSessionConfig(configs, callback); + } else { + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + createCaptureSession(surfaceList, callback); + } + } + + @TargetApi(VERSION_CODES.P) + private void createCaptureSessionWithSessionConfig( + List outputConfigs, CameraCaptureSession.StateCallback callback) + throws CameraAccessException { + cameraDevice.createCaptureSession( + new SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + outputConfigs, + Executors.newSingleThreadExecutor(), + callback)); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + @SuppressWarnings("deprecation") + private void createCaptureSession( + List surfaces, CameraCaptureSession.StateCallback callback) + throws CameraAccessException { + cameraDevice.createCaptureSession(surfaces, callback, null); } public void startVideoRecording(Result result) { From ef867badb7ab7cd79a86a8959ec5233e28f4a96a Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 11 Dec 2020 22:38:14 +0100 Subject: [PATCH 63/67] Apply feedback from bparrishMines --- .../plugins/camera/DartMessengerTest.java | 6 ++--- .../camera/example/ios/Flutter/.last_build_id | 1 - .../camera/camera/ios/Classes/CameraPlugin.m | 23 ++++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 3f182280a983..3b08a86d46c6 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -53,7 +53,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); - assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), call.method); + assertEquals("error", call.method); assertEquals("error description", call.argument("description")); } @@ -64,7 +64,7 @@ public void sendCameraInitializedEvent_includesPreviewSize() { List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); - assertEquals(EventType.INITIALIZED.toString().toLowerCase(), call.method); + assertEquals("initialized", call.method); assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); } @@ -76,7 +76,7 @@ public void sendCameraClosingEvent() { List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); - assertEquals(DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); + assertEquals("camera_closing", call.method); assertNull(call.argument("description")); } diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id deleted file mode 100644 index 1d0b0dc32be3..000000000000 --- a/packages/camera/camera/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index dbff59e64f9a..aa5dc522c5b5 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -194,6 +194,7 @@ @implementation FLTCam { } // Format used for video and image streaming. FourCharCode const videoFormat = kCVPixelFormatType_32BGRA; +NSString *const errorMethod = @"error"; - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset @@ -382,7 +383,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } } if (!CMSampleBufferDataIsReady(sampleBuffer)) { - [_methodChannel invokeMethod:@"error" arguments:@"sample buffer is not ready. Skipping sample"]; + [_methodChannel invokeMethod:errorMethod arguments:@"sample buffer is not ready. Skipping sample"]; return; } if (_isStreamingImages) { @@ -446,7 +447,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } if (_isRecording && !_isRecordingPaused) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" + [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; return; } @@ -530,7 +531,7 @@ - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_R - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" + [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; @@ -538,7 +539,7 @@ - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriterInput.readyForMoreMediaData) { if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { [_methodChannel - invokeMethod:@"error" + invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } } @@ -547,7 +548,7 @@ - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" + [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; @@ -555,7 +556,7 @@ - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { if (_audioWriterInput.readyForMoreMediaData) { if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { [_methodChannel - invokeMethod:@"error" + invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } } @@ -661,7 +662,7 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen _isStreamingImages = YES; } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; + [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are already streaming!"]; } } @@ -670,7 +671,7 @@ - (void)stopImageStream { _isStreamingImages = NO; _imageStreamHandler = nil; } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; + [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are not streaming!"]; } } @@ -690,7 +691,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { error:&error]; NSParameterAssert(_videoWriter); if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; + [_methodChannel invokeMethod:errorMethod arguments:error.description]; return NO; } NSDictionary *videoSettings = [NSDictionary @@ -744,7 +745,7 @@ - (void)setUpCaptureSessionForAudio { AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; + [_methodChannel invokeMethod:errorMethod arguments:error.description]; } // Setup the audio output. _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; @@ -756,7 +757,7 @@ - (void)setUpCaptureSessionForAudio { [_captureSession addOutput:_audioOutput]; _isAudioSetup = YES; } else { - [_methodChannel invokeMethod:@"error" + [_methodChannel invokeMethod:errorMethod arguments:@"Unable to add Audio input/output to session capture"]; _isAudioSetup = NO; } From 3e24d9ff0fd2ec9326afb3f314a37e08236059a1 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sat, 12 Dec 2020 08:36:12 +0100 Subject: [PATCH 64/67] Fix formatting issues --- .../java/io/flutter/plugins/camera/DartMessengerTest.java | 1 - packages/camera/camera/ios/Classes/CameraPlugin.m | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 3b08a86d46c6..a689f2b6128f 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,7 +7,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; -import io.flutter.plugins.camera.DartMessenger.EventType; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index aa5dc522c5b5..9455375b8524 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -383,7 +383,8 @@ - (void)captureOutput:(AVCaptureOutput *)output } } if (!CMSampleBufferDataIsReady(sampleBuffer)) { - [_methodChannel invokeMethod:errorMethod arguments:@"sample buffer is not ready. Skipping sample"]; + [_methodChannel invokeMethod:errorMethod + arguments:@"sample buffer is not ready. Skipping sample"]; return; } if (_isStreamingImages) { @@ -662,7 +663,8 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen _isStreamingImages = YES; } else { - [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are already streaming!"]; + [_methodChannel invokeMethod:errorMethod + arguments:@"Images from camera are already streaming!"]; } } From 46a1b2737797721d09e669c69e7d353606faf2fa Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sat, 12 Dec 2020 16:06:14 +0100 Subject: [PATCH 65/67] Removed redundant podspec files --- .../camera/example/ios/Flutter/Flutter.podspec | 18 ------------------ .../example/ios/Flutter/Flutter.podspec | 18 ------------------ 2 files changed, 36 deletions(-) delete mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec delete mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 5ca30416bac0..000000000000 --- a/packages/camera/camera/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' -end diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 5ca30416bac0..000000000000 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' -end From 7f266cd4f464c8000b94499f8e98b8bfefb4a4fb Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sat, 12 Dec 2020 16:13:57 +0100 Subject: [PATCH 66/67] Removed redundant ios files --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- 2 files changed, 16 deletions(-) delete mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - From c1c04a61a9db1abb77190dfa04b342599fdf53fe Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sat, 12 Dec 2020 17:08:48 +0100 Subject: [PATCH 67/67] Handle SecurityException --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 39d00ea2962c..306dd447cfb9 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -224,7 +224,7 @@ public void takePicture(@NonNull final Result result) { final File file; try { file = File.createTempFile("CAP", ".jpg", outputDir); - } catch (IOException e) { + } catch (IOException | SecurityException e) { result.error("cannotCreateFile", e.getMessage(), null); return; } @@ -373,7 +373,7 @@ public void startVideoRecording(Result result) { final File outputDir = applicationContext.getCacheDir(); try { videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); - } catch (IOException e) { + } catch (IOException | SecurityException e) { result.error("cannotCreateFile", e.getMessage(), null); return; }