-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support video source #48
Conversation
image.complete is not valid to check image is loaded. Ref: https://stackoverflow.com/questions/23657424/why-image-complete-property-always-return-true-even-if-there-is-no-src-tag Ref naver#44
@@ -38,7 +39,7 @@ const ERROR_TYPE = { | |||
}; | |||
|
|||
export default class PanoImageRenderer extends Component { | |||
constructor(image, width, height, sphericalConfig) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isVideo
is used to distinguish image
is image or video.
Other consideration
- image => content
- Issue: It's difficult to distinguish image from image if content is
string
.
- Issue: It's difficult to distinguish image from image if content is
- Add isVideo property on sphericalConfig
- Issue: isVideo is not related with sphericalConfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood.
|
||
this._onImageLoad = this._onImageLoad.bind(this); | ||
this._onImageError = this._onImageError.bind(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've replaced image
with content
.
media
was a candidate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the media element only contains video and audio, the name media is not appropriate for image and video.
I'm okay with unified loader name as content loader.
} | ||
|
||
setImage({image, imageType}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't separate a function into video and image. because their logic are almost same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
this._contentLoader = new VideoLoader(); | ||
} else { | ||
this._contentLoader = new ImageLoader(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contentLoader uses concept of inheritance. and use get/set common interface.
I've tried to combine imageLoader and videoLoader. But it is somewhat different logic. If being combined, code will be difficult to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with this.
@@ -145,19 +156,20 @@ export default class PanoImageRenderer extends Component { | |||
} | |||
|
|||
cancelLoadImage() { | |||
this._imageLoader.cancelLoadImage(); | |||
this._contentLoader.destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed cancelImage. and replaced it destroy.
this._contentLoader.get() | ||
.then(() => this._bindTexture()) | ||
.then(res) | ||
.catch(rej); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use catch
to check res
fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line with catch cannot resolve failing situation.
inst.bindTexture().then(() => {}).catch(() => {});
@@ -32,7 +33,7 @@ export default class PanoViewer extends Component { | |||
* @param {Array} [config.fovRange=[30, 110]] Range of controllable vertical field of view values <ko>제어 가능한 수직 field of view 값의 범위</ko> | |||
* @param {Function} [config.checkSupport] A function that returns a boolean value that determines whether the component is working. <ko>뷰어가 작동할 지 여부를 결정하는 부울 값을 반환하는 함수입니다.</ko> | |||
*/ | |||
constructor(container, options) { | |||
constructor(container, options = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script error will be prevented if options are omitted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good
src/PanoViewer/PanoViewer.js
Outdated
@@ -57,8 +58,19 @@ export default class PanoViewer extends Component { | |||
return this; | |||
} | |||
|
|||
if (!!options.image === !!options.video) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If image and video are both omitted or both specified, then trigger INVALID_RESOURCE
error. In other words, only one resource (image or video) can be specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great.
There should be documentation for the new event.
like below
/**
* Events that is fired when animation which is triggered by inertia is ended.
* @ko 관성에 의한 애니메이션 동작이 완료되었을때 발생하는 이벤트
* @name eg.view360.PanoViewer#animationEnd
* @event
* @example
*
* viwer.on({
* "animationEnd" : function(evt) {
* // animation is ended.
* });
*/
this jsdoc is written inside of _triggerEvent method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I will apply it on PanoViewer#error
src/PanoViewer/PanoViewer.js
Outdated
return { | ||
video: this._image, | ||
type: this._imageType | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be careful its return type is differ from getImage()
;
getVideo returns,
- video:
- type: projectionType (equirectangular or cubemap)
getImage returns
- image: Image Object
- imageType: projectionType (equirectangular or cubemap)
Both return projection type but it expose other interface(type/imageType) each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj As you mentioned in offline, I think it would be better not to expose type information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found imageType accessing code that use like below.
panoViewer.setImage({
image: image,
imageType: panoViewer.getImage().imageType
});
We need to decide name of imageType getter.
- projectionType
- imageType
- type
- and others?
src/PanoImageRenderer/ImageLoader.js
Outdated
} | ||
|
||
// promise for image | ||
return this.get(); | ||
} | ||
|
||
static _isMaybeLoaded(image) { | ||
return image && (image.width || image.height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
width and height property in image object is not read-only.
However, naturalWidth is read-only, so it is good to check that the image has been loaded.
The complete property is not appropriate because image objects that do not have src will also have true values.
I suggest that you apply the following statement.
return image && (image.naturalWidth !== 0);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj good information, thank you!
|
||
this._onImageLoad = this._onImageLoad.bind(this); | ||
this._onImageError = this._onImageError.bind(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the media element only contains video and audio, the name media is not appropriate for image and video.
I'm okay with unified loader name as content loader.
this._contentLoader = new VideoLoader(); | ||
} else { | ||
this._contentLoader = new ImageLoader(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with this.
} | ||
|
||
setImage({image, imageType}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
@@ -378,7 +390,7 @@ export default class PanoImageRenderer extends Component { | |||
} | |||
|
|||
renderWithQuaternion(quaternion, fieldOfView) { | |||
if (!this.hasRenderingContext()) { | |||
if (!this.isImageLoaded() || !this.hasRenderingContext()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isImageLoaded method seems like no longer belongs to the PanoImageRenderer.
How about removing isImageLoaded method from PanoImageRenderer and checking loading status with method on VideoLoader and ImageLoader?
like
this._imageLoader.isContentReady();
this._videoLoader.isContentReady();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj I got it!
@@ -32,7 +33,7 @@ export default class PanoViewer extends Component { | |||
* @param {Array} [config.fovRange=[30, 110]] Range of controllable vertical field of view values <ko>제어 가능한 수직 field of view 값의 범위</ko> | |||
* @param {Function} [config.checkSupport] A function that returns a boolean value that determines whether the component is working. <ko>뷰어가 작동할 지 여부를 결정하는 부울 값을 반환하는 함수입니다.</ko> | |||
*/ | |||
constructor(container, options) { | |||
constructor(container, options = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good
src/PanoViewer/PanoViewer.js
Outdated
@@ -57,8 +58,19 @@ export default class PanoViewer extends Component { | |||
return this; | |||
} | |||
|
|||
if (!!options.image === !!options.video) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great.
There should be documentation for the new event.
like below
/**
* Events that is fired when animation which is triggered by inertia is ended.
* @ko 관성에 의한 애니메이션 동작이 완료되었을때 발생하는 이벤트
* @name eg.view360.PanoViewer#animationEnd
* @event
* @example
*
* viwer.on({
* "animationEnd" : function(evt) {
* // animation is ended.
* });
*/
this jsdoc is written inside of _triggerEvent method.
src/PanoImageRenderer/VideoLoader.js
Outdated
export default class VideoLoader { | ||
constructor(video) { | ||
this._isLoaded = false; | ||
this._handlers = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj I added event handler for managing content load event. Because if not managed, it fires event although it has been destroyed.
@@ -57,8 +58,19 @@ export default class PanoViewer extends Component { | |||
return this; | |||
} | |||
|
|||
if (!!options.image && !!options.video) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj I had changed above condition if (!!options.image === !!options.video)
. but I changed it . because of allowing no content specified.
} | ||
|
||
return this._photoSphereRenderer.getContent(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj getVideo
returns HTMLVideoElement
* Init event handlers | ||
*/ | ||
this._image.onload = null; | ||
this._image.onerror = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj Initialize event handler for preventing event from being fired after destroy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to remove listeners which attached with addEventListener, just like in VideoLoader.js
content: this._image, | ||
isVideo: this._isVideo, | ||
projectionType: this._imageType | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj Renderer triggers IMAGE_LOADED event with param {content, isVideo, projectionType}
this._imageType = options.imageType || PanoImageRenderer.ImageType.EQUIRECTANGULAR; | ||
this._image = options.image || options.video; | ||
this._isVideo = !!options.video; | ||
this._projectionType = options.projectionType || PanoImageRenderer.ImageType.EQUIRECTANGULAR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj _imageType
-> _projectionType
on PanoViewer. It is not applied on PanoImageRenderer
src/PanoViewer/PanoViewer.js
Outdated
if (this._image && isVideo !== this._isVideo) { | ||
console.warn("Not support changing content type(Image <--> Video)"); | ||
return this; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj I'm begging your opinion for this.
In above code, if content type is differed then it will do nothing. I consider following way if set
fails.
- throw
- return false (if fails)
- console
- ignore
But each have each problem.
- User should try/catch
- Different
set
interface (others returns this) - User may not recognize it.
- User cannot judge what is the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.warn would be nice.
And it will be better to show this is temporary state with warning message.
"Currently not supporting changing content type(Image <--> Video)"
} | ||
); | ||
this._bindRendererHandler(); | ||
} | ||
|
||
_bindRendererHandler() { | ||
this._photoSphereRenderer.on(PanoImageRenderer.EVENTS.IMAGE_LOADED, e => { | ||
this.trigger(EVENTS.CONTENT_LOADED, e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj I added "contentLoaded" event.
this._contentLoader.get() | ||
.then(() => this._bindTexture()) | ||
.then(res) | ||
.catch(rej); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line with catch cannot resolve failing situation.
inst.bindTexture().then(() => {}).catch(() => {});
src/PanoImageRenderer/ImageLoader.js
Outdated
res(this._image); | ||
} else if (this._loadStatus === STATUS.LOADING) { | ||
ImageLoader._once(this._image, "load", () => { | ||
this._loadStatus = STATUS.LOADED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need to set state with getter method.
src/PanoImageRenderer/ImageLoader.js
Outdated
ImageLoader._once(this.image, "error", () => { | ||
rej("failed to load images."); | ||
ImageLoader._once(this._image, "error", () => { | ||
this._loadStatus = STATUS.ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need to set state with getter method.
* Init event handlers | ||
*/ | ||
this._image.onload = null; | ||
this._image.onerror = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to remove listeners which attached with addEventListener, just like in VideoLoader.js
src/PanoImageRenderer/VideoLoader.js
Outdated
res(this._video); | ||
} else { | ||
this._once("canplaythrough", () => { | ||
this._isLoaded = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to set loading status in setter method.
} | ||
|
||
set(video) { | ||
if (typeof video === "string") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting loading status is missing. like
this._once("canplaythrough", () => {
this._isLoaded = true;
});
and should check the case that the video element already triggered canplaythrough event.
in that case canplaythrough event will not triggered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@happyhj So I check it video status like below on getter.
else if (this._video.readyState === READY_STATUS.HAVE_ENOUGH_DATA) {
res(this._video);
}
const width = image.width;// imageWidth; | ||
const height = image.height;// imageHeight; | ||
const width = image.width || image.videoWidth;// imageWidth; | ||
const height = image.height || image.videoHeight;// imageHeight; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is safer to use read-only property like videoWidth of video element.
How about using naturalWidth and naturalHeight property for image element?
src/PanoViewer/PanoViewer.js
Outdated
if (this._image && isVideo !== this._isVideo) { | ||
console.warn("Not support changing content type(Image <--> Video)"); | ||
return this; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.warn would be nice.
And it will be better to show this is temporary state with warning message.
"Currently not supporting changing content type(Image <--> Video)"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
And I also reviewed test code.
this.inst = new ImageLoader("https://s.pstatic.net/static/www/mobile/edit/2015/0318/mobile_110629401121.png"); | ||
it("should get image URL as a parameter", function(done) { | ||
// this.inst = new ImageLoader("https://s.pstatic.net/static/www/mobile/edit/2015/0318/mobile_110629401121.png"); | ||
this.inst = new ImageLoader("./images/PanoViewer/waterpark_preview.jpg"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to use naturalWidth/Height property rather than width/height for checking image is fully loaded.
assert.isOk(image);
assert.isOk(image.complete);
assert.isNumber(image.naturalWidth);
assert.isNumber(image.naturalHeight);
assert.notEqual(image.naturalWidth, 0); // need to check dimension is not 0
assert.notEqual(image.naturalHeight, 0); // need to check dimension is not 0
and please add given, when, then annotation.
console.log(msg); | ||
done(); | ||
}); | ||
console.log(msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing assertion statement.
Calling done method means only the test is ended.
And should not call done method only in the reject callback.
If the reject callback is not called, this test will not end forever.
// Given | ||
// When | ||
this.inst = new ImageLoader(); | ||
|
||
this.inst.setImage("https://s.pstatic.net/static/www/mobile/edit/2015/0318/mobile_110629401121.png").then((img) => { | ||
return this.inst.set("./images/PanoViewer/waterpark_preview.jpg").then(img => { | ||
assert.isOk(img.complete); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing to call done method in asynchronous testing.
const inst = new ImageLoader(imgObj); // image is not loaded. | ||
|
||
// Then | ||
expect(inst._loadStatus).to.be.equal(1); // 2 is LOADING status |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this case without checking private property?
Because making change to _loadStatus will make this test break.
inst = new ImageLoader(imgObj); | ||
|
||
// Then | ||
expect(inst._loadStatus).to.be.equal(2); // 2 is LOADED status |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using private property, too.
|
||
it("should resolve image after image is loaded successfully", () => { | ||
// Given |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test cannot guarantee the image object delivered with resolve callback is fully loaded. It guarantees only resolve callback is calling.
How about below?
it("should resolve image after image is loaded successfully", (done) => {
// Given
// When
const inst = new ImageLoader("./images/PanoViewer/waterpark_preview.jpg");
inst.get()
.then((image) => {
// then
const isLoaded = image && image.naturalWidth !== 0;
expect(isLoaded, "resolve image after image has been loaded.").to.be.true;
done();
});
});
expect(this.inst).to.be.exist; | ||
this.inst.get().then(null, (msg)=> { | ||
console.log(msg); | ||
done(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if resolve is not called, test will not end.
this.inst.get().then(video => { | ||
assert.isOk(video); | ||
assert.isTrue(video instanceof HTMLVideoElement); | ||
done(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't suggest place done method inside of resolve callback.
What do you think about using Promise.prototype.finally?
change aync test can be stopped Ref naver#44
LGTM |
Issue
#44
Details
We need to discuss video interface on this branch (video#44)