Merge pull request #7254 from ZappoMan/hackathonMusicPlayer

Music Player
This commit is contained in:
Chris Collins 2016-03-07 09:01:58 -08:00
commit 86f60671a7
4 changed files with 474 additions and 0 deletions

View file

@ -0,0 +1,100 @@
//
// createNewMusicPlayerOnClick.js
//
// Created by Brad Hefta-Gaub on 3/3/16.
// Copyright 2016 High Fidelity, Inc.
//
// Entity Script that you can attach to any entity to have it spawn new "music players"
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function(){
var musicPlayerScript = Script.resolvePath("./musicPlayer.js");
var imageShader = Script.resolvePath("./imageShader.fs");
var defaultImage = Script.resolvePath("./defaultImage.jpg");
function getPositionToCreateEntity() {
var distance = 0.5;
var direction = Quat.getFront(Camera.orientation);
var offset = Vec3.multiply(distance, direction);
var placementPosition = Vec3.sum(Camera.position, offset);
var cameraPosition = Camera.position;
var HALF_TREE_SCALE = 16384;
var cameraOutOfBounds = Math.abs(cameraPosition.x) > HALF_TREE_SCALE || Math.abs(cameraPosition.y) > HALF_TREE_SCALE || Math.abs(cameraPosition.z) > HALF_TREE_SCALE;
var placementOutOfBounds = Math.abs(placementPosition.x) > HALF_TREE_SCALE || Math.abs(placementPosition.y) > HALF_TREE_SCALE || Math.abs(placementPosition.z) > HALF_TREE_SCALE;
if (cameraOutOfBounds && placementOutOfBounds) {
return null;
}
placementPosition.x = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.x));
placementPosition.y = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.y));
placementPosition.z = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.z));
return placementPosition;
}
function createNewIPOD() {
var iPodPosition = { x: 0, y: .5, z: 0};
var iPodDimensions = { x: 0.15, y: 0.3, z: 0.03 };
var overlayDimensions = { x: 0.13, y: 0.13, z: 0.001 };
var boxOverlayDimensions = { x: 0.13, y: 0.13, z: 0.0001 };
var iPodID = Entities.addEntity({
type: "Box",
name: "music player",
position: iPodPosition,
dimensions: iPodDimensions,
color: { red: 150, green: 150, blue: 150},
script: musicPlayerScript,
dynamic: true
});
print(iPodID);
var textID = Entities.addEntity({
type: "Text",
name: "now playing",
position: { x: 0, y: (0.5+0.07), z: 0.0222},
dimensions: overlayDimensions,
color: { red: 150, green: 150, blue: 150},
parentID: iPodID,
lineHeight: 0.01,
text: "Pause"
});
var newAlbumArt = JSON.stringify(
{
"ProceduralEntity": {
"version":2,
"shaderUrl":imageShader,
"uniforms":{"iSpeed":0,"iShell":1},
"channels":[defaultImage]
}
});
var albumArtID = Entities.addEntity({
type: "Box",
name: "album art",
position: { x: 0, y: (0.5-0.07), z: 0.01506},
dimensions: boxOverlayDimensions,
color: { red: 255, green: 255, blue: 255},
userData: newAlbumArt,
parentID: iPodID
});
Entities.editEntity(iPodID, { position: getPositionToCreateEntity() });
}
this.clickDownOnEntity = function(myID, mouseEvent) {
createNewIPOD();
};
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -0,0 +1,44 @@
float aspect(vec2 v) {
return v.x / v.y;
}
vec3 indexedTexture() {
vec2 uv = _position.xy;
uv += 0.5;
uv.y = 1.0 - uv.y;
float targetAspect = iWorldScale.x / iWorldScale.y;
float sourceAspect = aspect(iChannelResolution[0].xy);
float aspectCorrection = sourceAspect / targetAspect;
if (aspectCorrection > 1.0) {
float offset = aspectCorrection - 1.0;
float halfOffset = offset / 2.0;
uv.y -= halfOffset;
uv.y *= aspectCorrection;
} else {
float offset = 1.0 - aspectCorrection;
float halfOffset = offset / 2.0;
uv.x -= halfOffset;
uv.x /= aspectCorrection;
}
if (any(lessThan(uv, vec2(0.0)))) {
return vec3(0.0);
}
if (any(greaterThan(uv, vec2(1.0)))) {
return vec3(0.0);
}
vec4 color = texture(iChannel0, uv);
return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x));
}
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
if (_position.z > -0.49) {
discard;
}
specular = indexedTexture();
return 1.0;
}

View file

@ -0,0 +1,330 @@
//
// musicPlayer.js
//
// Created by Brad Hefta-Gaub on 3/3/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
(function() {
var imageShader = Script.resolvePath("./imageShader.fs");
var defaultImage = Script.resolvePath("./defaultImage.jpg");
var MAPPING_NAME = "com.highfidelity.musicPlayerEntity";
var PLAYLIST_URL = "https://spreadsheets.google.com/feeds/cells/1x-ceGPGHldkHadARABFWfujLPTOWzXJPhrf2bTwg2cQ/od6/public/basic?alt=json";
var SONG_VOLUME = 0.1;
var HEADPHONES_ATTACHMENT = {
modelURL: "https://s3.amazonaws.com/hifi-public/brad/musicplayer/headphones2-v2.fbx",
jointName: "Head",
translation: {"x": 0, "y": 0.19, "z": 0.06},
rotation: {"x":0,"y":0.7071067690849304,"z":0.7071067690849304,"w":0},
scale: 0.435,
isSoft: false
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Various helper functions...
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper function for returning either a value, or the default value if the value is undefined. This is
// is handing in parsing JSON where you don't know if the values have been set or not.
function valueOrDefault(value, defaultValue) {
if (value !== undefined) {
return value;
}
return defaultValue;
}
// return a random float between high and low
function randFloat(low, high) {
return low + Math.random() * (high - low);
}
// wears an attachment on MyAvatar
function wearAttachment(attachment) {
MyAvatar.attach(attachment.modelURL,
attachment.jointName,
attachment.translation,
attachment.rotation,
attachment.scale,
attachment.isSoft);
}
// un-wears an attachment from MyAvatar
function removeAttachment(attachment) {
var attachments = MyAvatar.attachmentData;
var i, l = attachments.length;
for (i = 0; i < l; i++) {
if (attachments[i].modelURL === attachment.modelURL) {
attachments.splice(i, 1);
MyAvatar.attachmentData = attachments;
break;
}
}
}
var _this;
MusicPlayer = function() {
_this = this;
this.equipped = false;
};
MusicPlayer.prototype = {
preload: function(entityID) {
print("preload");
//print("rotation:" + JSON.stringify(Quat.fromPitchYawRollDegrees(-90,180,0)));
this.entityID = entityID;
// Get the entities userData property, to see if someone has overridden any of our default settings
var userDataText = Entities.getEntityProperties(entityID, ["userData"]).userData;
//print(userDataText);
var userData = {};
if (userDataText !== "") {
//print("calling JSON.parse");
userData = JSON.parse(userDataText);
//print("userData:" + JSON.stringify(userData));
}
var musicPlayerUserData = valueOrDefault(userData.musicPlayer, {});
this.headphonesAttachment = valueOrDefault(musicPlayerUserData.headphonesAttachment, HEADPHONES_ATTACHMENT);
this.track = 0; // start at the first track
this.playlistURL = valueOrDefault(musicPlayerUserData.playlistURL, PLAYLIST_URL);
this.songVolume = valueOrDefault(musicPlayerUserData.songVolume, SONG_VOLUME);
this.songPlaying = false;
this.loadPlayList();
// Find my screen and any controlls
var children = Entities.getChildrenIDsOfJoint(entityID, 65535);
for (var child in children) {
var childID = children[child];
var childProperties = Entities.getEntityProperties(childID,["type", "name"]);
if (childProperties.type == "Text" && childProperties.name == "now playing") {
this.nowPlayingID = childID;
}
if (childProperties.type == "Box" && childProperties.name == "album art") {
this.albumArt = childID;
}
}
},
unload: function() {
print("unload");
if (_this.songInjector !== undefined) {
_this.songInjector.stop();
}
},
loadPlayList: function() {
print("loadPlayList");
var req = new XMLHttpRequest();
req.open("GET", _this.playlistURL, false);
req.send();
var entries = JSON.parse(req.responseText).feed.entry;
for (entry in entries) {
var cellDetails = entries[entry];
var cellName = cellDetails.title.$t;
var column = Number(cellName.charCodeAt(0)) - Number("A".charCodeAt(0));
var row = Number(cellName.slice(1)) - 1;
var cellContent = cellDetails.content.$t;
//print(JSON.stringify(cellDetails));
//print("["+column +"/"+ row +":"+cellContent+"]");
if (_this.playList === undefined) {
_this.playList = new Array();
}
if (_this.playList[row] === undefined) {
_this.playList[row] = { };
}
switch (column) {
case 0:
_this.playList[row].title = cellContent;
break;
case 1:
_this.playList[row].artist = cellContent;
break;
case 2:
_this.playList[row].album = cellContent;
break;
case 3:
_this.playList[row].url = cellContent;
_this.playList[row].sound = SoundCache.getSound(cellContent);
break;
case 4:
_this.playList[row].albumArtURL = cellContent;
break;
}
}
//print(req.responseText);
print(JSON.stringify(_this.playList));
},
startEquip: function(id, params) {
var whichHand = params[0]; // "left" or "right"
print("I am equipped in the " + whichHand + " hand....");
this.equipped = true;
this.hand = whichHand;
this.loadPlayList(); // reload the playlist in case...
this.mapHandButtons(whichHand);
wearAttachment(HEADPHONES_ATTACHMENT);
},
continueEquip: function(id, params) {
if (!this.equipped) {
return;
}
},
releaseEquip: function(id, params) {
print("I am NO LONGER equipped....");
this.hand = null;
this.equipped = false;
Controller.disableMapping(MAPPING_NAME);
removeAttachment(HEADPHONES_ATTACHMENT);
this.pause();
},
mapHandButtons: function(hand) {
var mapping = Controller.newMapping(MAPPING_NAME);
if (hand === "left") {
mapping.from(Controller.Standard.LS).peek().to(this.playOrPause);
mapping.from(Controller.Standard.LX).peek().to(this.seek);
mapping.from(Controller.Standard.LY).peek().to(this.volume);
} else {
mapping.from(Controller.Standard.RS).peek().to(this.playOrPause);
mapping.from(Controller.Standard.RX).peek().to(this.seek);
mapping.from(Controller.Standard.RY).peek().to(this.volume);
}
Controller.enableMapping(MAPPING_NAME);
},
playOrPause: function(value) {
print("[playOrPause: "+value+"]");
if (value === 1) {
if (!_this.songPlaying) {
_this.play();
} else {
_this.pause();
}
}
},
play: function() {
print("play current track:" + _this.track);
if (!_this.playList[_this.track].sound.downloaded) {
print("still waiting on track to download....");
return; // not yet ready
}
var statusText = "Song:" + _this.playList[_this.track].title + "\n" +
"Artist:" + _this.playList[_this.track].artist + "\n" +
"Album:" + _this.playList[_this.track].album;
Entities.editEntity(_this.nowPlayingID, { text: statusText });
var newAlbumArt = JSON.stringify(
{
"ProceduralEntity": {
"version":2,
"shaderUrl":imageShader,
"uniforms":{"iSpeed":0,"iShell":1},
"channels":[_this.playList[_this.track].albumArtURL]
}
});
Entities.editEntity(_this.albumArt, { userData: newAlbumArt });
_this.songInjector = Audio.playSound(_this.playList[_this.track].sound, {
position: MyAvatar.position,
volume: _this.songVolume,
loop: false,
localOnly: true
});
_this.songPlaying = true;
},
pause: function() {
print("pause");
Entities.editEntity(_this.nowPlayingID, { text: "Paused" });
if (_this.songInjector !== undefined) {
_this.songInjector.stop();
}
var newAlbumArt = JSON.stringify(
{
"ProceduralEntity": {
"version":2,
"shaderUrl":imageShader,
"uniforms":{"iSpeed":0,"iShell":1},
"channels":[defaultImage]
}
});
Entities.editEntity(_this.albumArt, { userData: newAlbumArt });
_this.songPlaying = false;
},
seek: function(value) {
print("[seek: " + value + "]");
if (value > 0.9) {
_this.next();
} else if (value < -0.9) {
_this.previous();
}
},
volume: function(value) {
print("adjusting volume disabled because of a bug in audio injectors....");
/*
var scaledValue = value / 20;
_this.songVolume += scaledValue;
print("[volume: " + value + "] new volume:" + _this.songVolume);
if (_this.songInjector !== undefined) {
print("[volume: attempting to set options....");
var newOptions = {
position: MyAvatar.position,
volume: _this.songVolume,
loop: false,
localOnly: true
};
_this.songInjector.options = newOptions;
print("[volume: attempting to set options.... RESULT:" + JSON.stringify(_this.songInjector.options));
}
*/
},
previous: function() {
print("[previous]");
_this.pause();
_this.track--;
if (_this.track < 0) {
_this.track = (_this.playList.length - 1);
}
_this.play();
},
next: function() {
print("[next]");
_this.pause();
_this.track++;
if (_this.track >= _this.playList.length) {
_this.track = 0;
}
_this.play();
},
};
// entity scripts always need to return a newly constructed object of our type
return new MusicPlayer();
});