From 5fb5cc68f2adfda0ec633246c1a222ca68a7cf91 Mon Sep 17 00:00:00 2001 From: Teck Liew Date: Mon, 1 Jul 2019 19:16:51 -0700 Subject: [PATCH 1/7] add pattern-shaka-video-player --- pattern-shaka-video-player/.gitignore | 15 ++++ pattern-shaka-video-player/README.md | 9 +++ pattern-shaka-video-player/package.json | 43 +++++++++++ .../resources/ilibmanifest.json | 3 + pattern-shaka-video-player/src/App/App.js | 27 +++++++ .../src/App/App.module.less | 3 + .../src/App/package.json | 3 + .../src/components/ShakaVideoPlayer.js | 76 +++++++++++++++++++ pattern-shaka-video-player/src/index.js | 12 +++ .../src/views/MainPanel.js | 19 +++++ 10 files changed, 210 insertions(+) create mode 100644 pattern-shaka-video-player/.gitignore create mode 100644 pattern-shaka-video-player/README.md create mode 100644 pattern-shaka-video-player/package.json create mode 100644 pattern-shaka-video-player/resources/ilibmanifest.json create mode 100644 pattern-shaka-video-player/src/App/App.js create mode 100644 pattern-shaka-video-player/src/App/App.module.less create mode 100644 pattern-shaka-video-player/src/App/package.json create mode 100644 pattern-shaka-video-player/src/components/ShakaVideoPlayer.js create mode 100644 pattern-shaka-video-player/src/index.js create mode 100644 pattern-shaka-video-player/src/views/MainPanel.js diff --git a/pattern-shaka-video-player/.gitignore b/pattern-shaka-video-player/.gitignore new file mode 100644 index 00000000..49c03385 --- /dev/null +++ b/pattern-shaka-video-player/.gitignore @@ -0,0 +1,15 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +node_modules + +# testing +coverage + +# production +build +dist + +# misc +.DS_Store +npm-debug.log diff --git a/pattern-shaka-video-player/README.md b/pattern-shaka-video-player/README.md new file mode 100644 index 00000000..6ff842de --- /dev/null +++ b/pattern-shaka-video-player/README.md @@ -0,0 +1,9 @@ +## Shaka Video Player pattern + +A sample Enact application that demonstrates how to use [shaka-player](https://shaka-player-demo.appspot.com/docs/api/index.html). + +Run `npm install` then `npm run serve` to have the app running on [http://localhost:8080](http://localhost:8080), where you can view it in your browser. + +--- + +This project was bootstrapped with the Enact [cli](https://github.com/enactjs/cli). diff --git a/pattern-shaka-video-player/package.json b/pattern-shaka-video-player/package.json new file mode 100644 index 00000000..3b8256f4 --- /dev/null +++ b/pattern-shaka-video-player/package.json @@ -0,0 +1,43 @@ +{ + "name": "shaka-app", + "version": "1.0.0", + "description": "A general template for an Enact Moonstone application.", + "author": "", + "main": "src/index.js", + "scripts": { + "serve": "enact serve", + "pack": "enact pack", + "pack-p": "enact pack -p", + "watch": "enact pack --watch", + "clean": "enact clean", + "lint": "enact lint .", + "license": "enact license", + "test": "enact test", + "test-watch": "enact test --watch" + }, + "license": "UNLICENSED", + "private": true, + "repository": "", + "enact": { + "theme": "moonstone" + }, + "eslintConfig": { + "extends": "enact" + }, + "eslintIgnore": [ + "node_modules/*", + "build/*", + "dist/*" + ], + "dependencies": { + "@enact/core": "^2.0.0", + "@enact/i18n": "^2.0.0", + "@enact/moonstone": "^2.0.0", + "@enact/spotlight": "^2.0.0", + "@enact/ui": "^2.0.0", + "prop-types": "^15.6.2", + "react": "^16.7.0", + "react-dom": "^16.7.0", + "shaka-player": "^2.5.2" + } +} diff --git a/pattern-shaka-video-player/resources/ilibmanifest.json b/pattern-shaka-video-player/resources/ilibmanifest.json new file mode 100644 index 00000000..5916671d --- /dev/null +++ b/pattern-shaka-video-player/resources/ilibmanifest.json @@ -0,0 +1,3 @@ +{ + "files": [] +} \ No newline at end of file diff --git a/pattern-shaka-video-player/src/App/App.js b/pattern-shaka-video-player/src/App/App.js new file mode 100644 index 00000000..960f23e7 --- /dev/null +++ b/pattern-shaka-video-player/src/App/App.js @@ -0,0 +1,27 @@ +import kind from '@enact/core/kind'; +import MoonstoneDecorator from '@enact/moonstone/MoonstoneDecorator'; +import Panels from '@enact/moonstone/Panels'; +import React from 'react'; + +import MainPanel from '../views/MainPanel'; + +import css from './App.module.less'; + +const App = kind({ + name: 'App', + + styles: { + css, + className: 'app' + }, + + render: (props) => ( +
+ + + +
+ ) +}); + +export default MoonstoneDecorator(App); diff --git a/pattern-shaka-video-player/src/App/App.module.less b/pattern-shaka-video-player/src/App/App.module.less new file mode 100644 index 00000000..23782def --- /dev/null +++ b/pattern-shaka-video-player/src/App/App.module.less @@ -0,0 +1,3 @@ +.app { + // styles can be put here +} diff --git a/pattern-shaka-video-player/src/App/package.json b/pattern-shaka-video-player/src/App/package.json new file mode 100644 index 00000000..add0ba2a --- /dev/null +++ b/pattern-shaka-video-player/src/App/package.json @@ -0,0 +1,3 @@ +{ + "main": "App.js" +} \ No newline at end of file diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js new file mode 100644 index 00000000..d4ec6381 --- /dev/null +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -0,0 +1,76 @@ +import hoc from '@enact/core/hoc'; +import VideoPlayer from "@enact/moonstone/VideoPlayer"; +import React from 'react'; +import PropTypes from 'prop-types'; +import shaka from 'shaka-player'; + +function initPlayer(config, manifestUri) { + // Create a Player instance. + const video = document.querySelector('video'); + const player = new shaka.Player(video); + + // Listen for error events. + player.addEventListener('error', onErrorEvent); + + // Try to load a manifest. + // This is an asynchronous process. + player + .load(manifestUri) + .then(function() { + // This runs if the asynchronous load is successful. + console.log('The video has now been loaded!'); + }) + .catch(onError); // onError is executed if the asynchronous load fails. + // Configure player + player.configure(config); +} + +function onErrorEvent(event) { + // Extract the shaka.util.Error object from the event. + onError(event.detail); +} + +function onError(error) { + // Log the error. + console.error('Error code', error.code, 'object', error); +} + +const defaultConfig = { + preferredAudioLanguage: 'en-US', + playRangeStart: 420 +}; + +const ShakaPlayerDecorator = hoc(defaultConfig, (config, Wrapped) => { + return class extends React.Component { + static displayName = 'ShakaPlayerDecorator'; + + static propTypes = { + manifestUri: PropTypes.string + }; + + componentDidMount () { + // Install built-in polyfills to patch browser incompatibilities. + shaka.polyfill.installAll(); + + // Check to see if the browser supports the basic APIs Shaka needs. + if (shaka.Player.isBrowserSupported()) { + // Everything looks good! + initPlayer(config, this.props.manifestUri); + } else { + // This browser does not have the minimum set of APIs we need. + console.error('Browser not supported!'); + } + } + + render () { + const props = Object.assign({}, this.props); + delete props.manifestUri; + + return + } + } +}); + +const ShakaVideoPlayer = ShakaPlayerDecorator(VideoPlayer); + +export default ShakaVideoPlayer; diff --git a/pattern-shaka-video-player/src/index.js b/pattern-shaka-video-player/src/index.js new file mode 100644 index 00000000..2c28735a --- /dev/null +++ b/pattern-shaka-video-player/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import {render} from 'react-dom'; +import App from './App'; + +const appElement = (); + +// In a browser environment, render instead of exporting +if (typeof window !== 'undefined') { + render(appElement, document.getElementById('root')); +} + +export default appElement; diff --git a/pattern-shaka-video-player/src/views/MainPanel.js b/pattern-shaka-video-player/src/views/MainPanel.js new file mode 100644 index 00000000..01a633b6 --- /dev/null +++ b/pattern-shaka-video-player/src/views/MainPanel.js @@ -0,0 +1,19 @@ +import kind from '@enact/core/kind'; +import {Panel, Header} from '@enact/moonstone/Panels'; +import React from 'react'; +import ShakaVideoPlayer from '../components/ShakaVideoPlayer'; + +const sintelManifestUri = 'https://storage.googleapis.com/shaka-demo-assets/sintel/dash.mpd'; + +const MainPanel = kind({ + name: 'MainPanel', + + render: (props) => ( + +
+ + + ) +}); + +export default MainPanel; From 7e0744312dd0c3d13b424f846d4243ac65d0e18a Mon Sep 17 00:00:00 2001 From: Teck Liew Date: Tue, 2 Jul 2019 16:32:07 -0700 Subject: [PATCH 2/7] spaces to tabs and move initPlayer into decorator --- .../src/components/ShakaVideoPlayer.js | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js index d4ec6381..8d973b2c 100644 --- a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -4,69 +4,74 @@ import React from 'react'; import PropTypes from 'prop-types'; import shaka from 'shaka-player'; -function initPlayer(config, manifestUri) { - // Create a Player instance. - const video = document.querySelector('video'); - const player = new shaka.Player(video); - - // Listen for error events. - player.addEventListener('error', onErrorEvent); - - // Try to load a manifest. - // This is an asynchronous process. - player - .load(manifestUri) - .then(function() { - // This runs if the asynchronous load is successful. - console.log('The video has now been loaded!'); - }) - .catch(onError); // onError is executed if the asynchronous load fails. - // Configure player - player.configure(config); -} - function onErrorEvent(event) { - // Extract the shaka.util.Error object from the event. - onError(event.detail); + // Extract the shaka.util.Error object from the event. + onError(event.detail); } function onError(error) { - // Log the error. - console.error('Error code', error.code, 'object', error); + // Log the error. + console.error('Error code', error.code, 'object', error); } const defaultConfig = { - preferredAudioLanguage: 'en-US', - playRangeStart: 420 + preferredAudioLanguage: 'en-US', + playRangeStart: 420 }; const ShakaPlayerDecorator = hoc(defaultConfig, (config, Wrapped) => { return class extends React.Component { - static displayName = 'ShakaPlayerDecorator'; + static displayName = 'ShakaPlayerDecorator'; + + static propTypes = { + manifestUri: PropTypes.string + }; + + componentDidMount () { + // Install built-in polyfills to patch browser incompatibilities. + shaka.polyfill.installAll(); - static propTypes = { - manifestUri: PropTypes.string - }; + // Check to see if the browser supports the basic APIs Shaka needs. + if (shaka.Player.isBrowserSupported()) { + // Everything looks good! + this.initPlayer(this.props.manifestUri, this.videoNode); + } else { + // This browser does not have the minimum set of APIs we need. + console.error('Browser not supported!'); + } + } + + initPlayer = () => { + // Create a Player instance. + const player = new shaka.Player(this.videoNode); + + // Listen for error events. + player.addEventListener('error', onErrorEvent); - componentDidMount () { - // Install built-in polyfills to patch browser incompatibilities. - shaka.polyfill.installAll(); + // Try to load a manifest. + // This is an asynchronous process. + player + .load(this.props.manifestUri) + .then(function() { + // This runs if the asynchronous load is successful. + console.log('The video has now been loaded!'); + }) + .catch(onError); // onError is executed if the asynchronous load fails. + // Configure player + player.configure(config); + } - // Check to see if the browser supports the basic APIs Shaka needs. - if (shaka.Player.isBrowserSupported()) { - // Everything looks good! - initPlayer(config, this.props.manifestUri); - } else { - // This browser does not have the minimum set of APIs we need. - console.error('Browser not supported!'); - } - } + setWrappedRef = (node) => { + if (node && node.getVideoNode) { + this.videoNode = node.getVideoNode().media; + } + } render () { const props = Object.assign({}, this.props); - delete props.manifestUri; + delete props.manifestUri; - return + return } } }); From 5da15ec2ec322a16b387bec7cac84f4f5ce60c5a Mon Sep 17 00:00:00 2001 From: Teck Liew Date: Wed, 3 Jul 2019 13:25:51 -0700 Subject: [PATCH 3/7] config for shaka player --- .../src/components/ShakaVideoPlayer.js | 39 +++++++++---------- .../src/views/MainPanel.js | 10 ++++- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js index 8d973b2c..1e2b98d7 100644 --- a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -4,27 +4,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import shaka from 'shaka-player'; -function onErrorEvent(event) { - // Extract the shaka.util.Error object from the event. - onError(event.detail); -} - -function onError(error) { - // Log the error. - console.error('Error code', error.code, 'object', error); -} - -const defaultConfig = { - preferredAudioLanguage: 'en-US', - playRangeStart: 420 -}; - -const ShakaPlayerDecorator = hoc(defaultConfig, (config, Wrapped) => { +const ShakaPlayerDecorator = hoc((config, Wrapped) => { return class extends React.Component { static displayName = 'ShakaPlayerDecorator'; static propTypes = { - manifestUri: PropTypes.string + manifestUri: PropTypes.string, + config: PropTypes.object }; componentDidMount () { @@ -41,12 +27,22 @@ const ShakaPlayerDecorator = hoc(defaultConfig, (config, Wrapped) => { } } + onErrorEvent = (event) => { + // Extract the shaka.util.Error object from the event. + this.onError(event.detail); + } + + onError = (error) => { + // Log the error. + console.error('Error code', error.code, 'object', error); + } + initPlayer = () => { // Create a Player instance. const player = new shaka.Player(this.videoNode); // Listen for error events. - player.addEventListener('error', onErrorEvent); + player.addEventListener('error', this.onErrorEvent); // Try to load a manifest. // This is an asynchronous process. @@ -56,9 +52,10 @@ const ShakaPlayerDecorator = hoc(defaultConfig, (config, Wrapped) => { // This runs if the asynchronous load is successful. console.log('The video has now been loaded!'); }) - .catch(onError); // onError is executed if the asynchronous load fails. - // Configure player - player.configure(config); + .catch(this.onError); // onError is executed if the asynchronous load fails. + // Configuration for the player here https://shaka-player-demo.appspot.com/docs/api/tutorial-config.html + const playerConfig = {...config, ...this.props.config}; + player.configure(playerConfig); } setWrappedRef = (node) => { diff --git a/pattern-shaka-video-player/src/views/MainPanel.js b/pattern-shaka-video-player/src/views/MainPanel.js index 01a633b6..624a0768 100644 --- a/pattern-shaka-video-player/src/views/MainPanel.js +++ b/pattern-shaka-video-player/src/views/MainPanel.js @@ -4,6 +4,10 @@ import React from 'react'; import ShakaVideoPlayer from '../components/ShakaVideoPlayer'; const sintelManifestUri = 'https://storage.googleapis.com/shaka-demo-assets/sintel/dash.mpd'; +const playerConfig = { + preferredAudioLanguage: 'en-US', + playRangeStart: 420 +}; const MainPanel = kind({ name: 'MainPanel', @@ -11,7 +15,11 @@ const MainPanel = kind({ render: (props) => (
- + ) }); From f32ae8f534857fcced6e441f8315b01125de9a64 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Fri, 5 Jul 2019 10:17:10 -0700 Subject: [PATCH 4/7] bit of clean up Signed-off-by: Ryan Duffy --- .../src/components/ShakaVideoPlayer.js | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js index 1e2b98d7..bc623f37 100644 --- a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -1,17 +1,19 @@ +import {forward, handle} from '@enact/core/handle'; import hoc from '@enact/core/hoc'; import VideoPlayer from "@enact/moonstone/VideoPlayer"; -import React from 'react'; import PropTypes from 'prop-types'; +import React from 'react'; import shaka from 'shaka-player'; const ShakaPlayerDecorator = hoc((config, Wrapped) => { return class extends React.Component { - static displayName = 'ShakaPlayerDecorator'; + static displayName = 'ShakaPlayerDecorator' static propTypes = { + config: PropTypes.object, manifestUri: PropTypes.string, - config: PropTypes.object - }; + onError: PropTypes.func + } componentDidMount () { // Install built-in polyfills to patch browser incompatibilities. @@ -20,7 +22,7 @@ const ShakaPlayerDecorator = hoc((config, Wrapped) => { // Check to see if the browser supports the basic APIs Shaka needs. if (shaka.Player.isBrowserSupported()) { // Everything looks good! - this.initPlayer(this.props.manifestUri, this.videoNode); + this.initPlayer(); } else { // This browser does not have the minimum set of APIs we need. console.error('Browser not supported!'); @@ -32,10 +34,9 @@ const ShakaPlayerDecorator = hoc((config, Wrapped) => { this.onError(event.detail); } - onError = (error) => { - // Log the error. - console.error('Error code', error.code, 'object', error); - } + onError = handle( + forward('onError') + ).bindAs(this, 'handleError') initPlayer = () => { // Create a Player instance. @@ -48,24 +49,30 @@ const ShakaPlayerDecorator = hoc((config, Wrapped) => { // This is an asynchronous process. player .load(this.props.manifestUri) - .then(function() { + .then(function () { // This runs if the asynchronous load is successful. console.log('The video has now been loaded!'); }) .catch(this.onError); // onError is executed if the asynchronous load fails. - // Configuration for the player here https://shaka-player-demo.appspot.com/docs/api/tutorial-config.html + + // Configuration for the player here + // https://shaka-player-demo.appspot.com/docs/api/tutorial-config.html const playerConfig = {...config, ...this.props.config}; player.configure(playerConfig); } setWrappedRef = (node) => { - if (node && node.getVideoNode) { - this.videoNode = node.getVideoNode().media; + if (node && node.getVideoNode) { + // By default, moonstone/VideoPlayer using ui/Media for its videoComponent. To get + // the underlying node, we're using the private `media` member. + this.videoNode = node.getVideoNode().media; + } } - } render () { - const props = Object.assign({}, this.props); + const props = {...this.props}; + + delete props.config; delete props.manifestUri; return From a155db670e47c22561775c58f549be7bfcec2fcd Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Fri, 5 Jul 2019 10:24:54 -0700 Subject: [PATCH 5/7] fix up exports Signed-off-by: Ryan Duffy --- .../src/components/ShakaVideoPlayer.js | 10 +++++++--- pattern-shaka-video-player/src/views/MainPanel.js | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js index bc623f37..b6986163 100644 --- a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -5,9 +5,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import shaka from 'shaka-player'; -const ShakaPlayerDecorator = hoc((config, Wrapped) => { +const ShakaVideoPlayerDecorator = hoc((config, Wrapped) => { return class extends React.Component { - static displayName = 'ShakaPlayerDecorator' + static displayName = 'ShakaVideoPlayerDecorator' static propTypes = { config: PropTypes.object, @@ -80,6 +80,10 @@ const ShakaPlayerDecorator = hoc((config, Wrapped) => { } }); -const ShakaVideoPlayer = ShakaPlayerDecorator(VideoPlayer); +const ShakaVideoPlayer = ShakaVideoPlayerDecorator(VideoPlayer); export default ShakaVideoPlayer; +export { + ShakaVideoPlayer, + ShakaVideoPlayerDecorator +}; diff --git a/pattern-shaka-video-player/src/views/MainPanel.js b/pattern-shaka-video-player/src/views/MainPanel.js index 624a0768..aa9e17dc 100644 --- a/pattern-shaka-video-player/src/views/MainPanel.js +++ b/pattern-shaka-video-player/src/views/MainPanel.js @@ -16,7 +16,6 @@ const MainPanel = kind({
From e2d049505210e94d2bffef65b75f5909c30a812c Mon Sep 17 00:00:00 2001 From: Teck Liew Date: Thu, 18 Jul 2019 16:10:06 -0700 Subject: [PATCH 6/7] update dependencies and documentation --- pattern-shaka-video-player/package.json | 19 ++++++++++--------- .../src/components/ShakaVideoPlayer.js | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pattern-shaka-video-player/package.json b/pattern-shaka-video-player/package.json index 3b8256f4..e60b0769 100644 --- a/pattern-shaka-video-player/package.json +++ b/pattern-shaka-video-player/package.json @@ -1,8 +1,8 @@ { "name": "shaka-app", "version": "1.0.0", - "description": "A general template for an Enact Moonstone application.", - "author": "", + "description": "A sample Enact app that showcases a VideoPlayer powered by Shaka Player", + "author": "@teckliew", "main": "src/index.js", "scripts": { "serve": "enact serve", @@ -15,7 +15,7 @@ "test": "enact test", "test-watch": "enact test --watch" }, - "license": "UNLICENSED", + "license": "Apache-2.0", "private": true, "repository": "", "enact": { @@ -30,14 +30,15 @@ "dist/*" ], "dependencies": { - "@enact/core": "^2.0.0", - "@enact/i18n": "^2.0.0", - "@enact/moonstone": "^2.0.0", - "@enact/spotlight": "^2.0.0", + "@enact/core": "^3.0.0-beta.1", + "@enact/i18n": "^3.0.0-beta.1", + "@enact/moonstone": "^3.0.0-beta.1", + "@enact/spotlight": "^3.0.0-beta.1", "@enact/ui": "^2.0.0", + "ilib": "^14.3.0", "prop-types": "^15.6.2", - "react": "^16.7.0", - "react-dom": "^16.7.0", + "react": "^16.8.0", + "react-dom": "^16.8.0", "shaka-player": "^2.5.2" } } diff --git a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js index b6986163..cd44526e 100644 --- a/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js +++ b/pattern-shaka-video-player/src/components/ShakaVideoPlayer.js @@ -39,7 +39,7 @@ const ShakaVideoPlayerDecorator = hoc((config, Wrapped) => { ).bindAs(this, 'handleError') initPlayer = () => { - // Create a Player instance. + // Create a Player instance with videoNode. const player = new shaka.Player(this.videoNode); // Listen for error events. @@ -63,8 +63,8 @@ const ShakaVideoPlayerDecorator = hoc((config, Wrapped) => { setWrappedRef = (node) => { if (node && node.getVideoNode) { - // By default, moonstone/VideoPlayer using ui/Media for its videoComponent. To get - // the underlying node, we're using the private `media` member. + // By default, moonstone/VideoPlayer uses ui/Media for its videoComponent. To get + // the underlying