SudoPod

From Sudo Room
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

SudoPod-snap.jpgSudoPod-web.jpg

A basic streaming audio player with integrated API.

package
{
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.media.SoundTransform;
	import flash.display.Sprite;
	import flash.media.SoundMixer;
	import flash.display.MovieClip;
	import flash.net.URLRequest;
	import flash.media.Sound;
	import flash.events.ProgressEvent;
	import flash.media.SoundChannel;
	import flash.utils.ByteArray;
	import flash.display.Graphics;
	import flash.events.IOErrorEvent;
	import flash.display.NativeWindow;
	import flash.events.ServerSocketConnectEvent;
	import flash.net.Socket;
	import flash.events.OutputProgressEvent;
	import flash.net.ServerSocket;
	
	public class sudoPod extends MovieClip
	{
		public var volumeDial:Sprite;
		
		private var _volume:Number = 0.5;
		
		private var window:NativeWindow;
		
		private var audioPath:String = "http://sudoradio.com:8000/";
		private var sound:Sound;
		private var soundChannel:SoundChannel;
		
		private var playbackActive:Boolean;
		
		private var localhostSocket:ServerSocket;
		// TODO: Update to custom class using vectors
		private var localhostClients:Object;
		
		private var lastX:Number;

		public function sudoPod() 
		{
			initUI();
			initSound();
			initLocalhost();
		}
		
		public function get volume():Number
		{
			return _volume;
		}
		
		public function set volume(level:Number):void
		{
			_volume = Math.max(0, Math.min(1, level));
			
			// Add SO for remembering last volume on restart
			// Add volume tween/fader
			
			var t:SoundTransform = SoundMixer.soundTransform;
			t.volume = _volume;
			SoundMixer.soundTransform = t;
			
			volumeDial.rotation = _volume * 120.0;			
		}
		
		private function update():void
		{
			drawSpectrum();
		}
		
		private function initUI():void
		{
			volumeDial.rotation = _volume * 120.0;
			
			volumeDial.addEventListener(MouseEvent.MOUSE_DOWN, dialHandler);
			
			window = stage.nativeWindow;
			window.stage.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
			
			this.addEventListener(MouseEvent.MOUSE_WHEEL, wheelHandler);
			this.addEventListener(Event.ENTER_FRAME, frameHandler);
		}
		
		private function initSound():void
		{
			sound = new Sound();
			sound.addEventListener(Event.COMPLETE, completeHandler);
			sound.addEventListener(Event.ID3, id3Handler);
			sound.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			sound.addEventListener(ProgressEvent.PROGRESS, progressHandler);
			
			volume = _volume;
			
			playbackActive = true;
			loadSound(audioPath);
		}
		
		private function initLocalhost():void
		{
			localhostClients = new Object();
			
			localhostSocket = new ServerSocket();
			localhostSocket.addEventListener(Event.CONNECT, localhostConnect);
			localhostSocket.addEventListener(Event.CLOSE, localhostClose);
			localhostSocket.bind(21337, "0.0.0.0");
			localhostSocket.listen();
		}
		
		private function startPlayback():void
		{
			if (!playbackActive)
			{
				playbackActive = true;
				initSound();
			}
		}
		
		private function stopPlayback():void
		{
			if (playbackActive)
			{
				playbackActive = false;
				soundChannel.stop();
				
				sound.removeEventListener(Event.COMPLETE, completeHandler);
				sound.removeEventListener(Event.ID3, id3Handler);
				sound.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
				sound.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
				sound.close();
			}
		}
		
		private function loadSound(path:String):void
		{
			audioPath = path;
			sound.load(new URLRequest(audioPath));
			
			soundChannel = sound.play();
		}
		
		private function drawSpectrum():void
		{
			var bytes:ByteArray = new ByteArray();
			
			var g:Graphics = spectrumGraph.graphics;
			g.clear();
			
			SoundMixer.computeSpectrum(bytes, true, 4);
			renderBytes(g, [0x0066CC, 0x00CC66], bytes);
			
			 SoundMixer.computeSpectrum(bytes, false, 0);
			renderBytes(g, [0x6600CC, 0xCC0066], bytes);
		}
		
		private function renderBytes(g:Graphics, c:Array, bytes:ByteArray):void
		{
			const PLOT_WIDTH:int = 200;
            const CHANNEL_LENGTH:int = 256;
			
			const L_OFF = -0;
			const R_OFF = 0;
            
            g.lineStyle(0, c[0], 0.8);
            g.beginFill(c[0], 0.5);
            g.moveTo(L_OFF, CHANNEL_LENGTH * 2);
            
            var n:Number = 0;
            
            for (var i:int = 0; i < CHANNEL_LENGTH; i++) {
                n = (bytes.readFloat() * PLOT_WIDTH);
                g.lineTo(-n + L_OFF, (CHANNEL_LENGTH * 2) - (i * 2));
            }

            g.lineTo(L_OFF, 0);
            g.endFill();
 
 			
            g.lineStyle(0, c[1], 0.8);
            g.beginFill(c[1], 0.5);
            g.moveTo(R_OFF, CHANNEL_LENGTH * 2);
            
            for (i = CHANNEL_LENGTH; i > 0; i--) {
                n = (bytes.readFloat() * PLOT_WIDTH);
                g.lineTo(n + R_OFF, i * 2);
            }
  
            g.lineTo(R_OFF, 0);
            g.endFill();
		}
		
		private function frameHandler(e:Event):void
		{
			update();
		}
		
		private function downHandler(e:MouseEvent):void
		{
			window.startMove();
		}
		
		private function dialHandler(e:MouseEvent):void
		{
			e.stopPropagation();
			
			lastX = mouseX;
			
			volumeDial.addEventListener(MouseEvent.MOUSE_MOVE, dialMoveHandler);
			this.addEventListener(MouseEvent.MOUSE_UP, upHandler);
		}
		
		private function upHandler(e:MouseEvent):void
		{
			volumeDial.removeEventListener(MouseEvent.MOUSE_MOVE, dialMoveHandler);
			this.removeEventListener(MouseEvent.MOUSE_UP, upHandler);
		}
		
		private function dialMoveHandler(e:MouseEvent):void
		{
			volume += (mouseX - lastX) / 100.0;
			lastX = mouseX;
		}
		
		private function wheelHandler(e:MouseEvent):void
		{
			volume += e.delta / 100.0;
		}
		
		private function completeHandler(e:Event):void
		{
			trace("Complete:", e);
		}
		
		private function id3Handler(e:Event):void
		{
			trace("ID3:", e);
		}
		
		private function ioErrorHandler(e:Event):void
		{
			trace("IO Error:", e);
		}
		
		private function progressHandler(e:ProgressEvent):void
		{
			// trace("Progress:", e);
			/*
			bytesLoadedText.text = e.bytesLoaded.toString();
			bytesTotalText.text = e.bytesTotal.toString();
			
			var p:Number = e.bytesLoaded / e.bytesTotal;
			percentLoadedText.text = Math.round(p * 100.0).toString() + "%";
			*/
		}
		
		
		/////////////////////////////////////////////////////////////
		/////////////////// localhost server ////////////////////////
		/////////////////////////////////////////////////////////////
		
		private function localhostConnect(e:ServerSocketConnectEvent):void
		{
			var socket:Socket = e.socket as Socket;
			localhostClients[socket.remoteAddress + socket.remotePort] = socket;
			
			socket.addEventListener(ProgressEvent.SOCKET_DATA, localhostSocketData);
			socket.addEventListener(Event.CLOSE, localhostClientClose);
			socket.addEventListener(IOErrorEvent.IO_ERROR, localhostIOError);
			socket.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, localhostProgress);
			
			trace("Connect: " + socket.remoteAddress + ":" + socket.remotePort);
		}
		
		private function closeLocalhostSocket(socket:Socket):void
		{
			trace("Close: " + socket.remoteAddress + ":" + socket.remotePort);
			
			socket.removeEventListener(ProgressEvent.SOCKET_DATA, localhostSocketData);
			socket.removeEventListener(Event.CLOSE, localhostClientClose);
			socket.removeEventListener(IOErrorEvent.IO_ERROR, localhostIOError);
			socket.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, localhostProgress);
			
			localhostClients[socket.remoteAddress + socket.remotePort] = null;
			
			if (socket.connected)
				socket.close();
			
			socket = null;
		}
		
		private function localhostProgress(e:OutputProgressEvent):void
		{
			if (e.bytesPending == 0)
			{
				var socket:Socket = e.target as Socket;
				closeLocalhostSocket(socket);
			}
		}
		
		private function genHTML():String
		{
			var html:String = "";
			html += "<html>\n"
			html += "  <head>\n";
			html += "    <title>sudoPod API</title>\n";
			html += "  </head>\n";
			html += "  <body>\n";
			html += "    <h1>sudoPod</h1>\n";
			html += "    <p>\n";
			html += "      <button onclick='window.location = \"/volumeUp\"'>Volume +</button><br/><br/>\n";
			html += "      Volume: " + Math.round(volume * 100.0).toString() + "%<br/><br/>\n";
			html += "      <button onclick='window.location = \"/volumeDown\"'>Volume -</button>\n";
			html += "    </p><br/<br/>\n";
			html += "    <p>\n";
			html += "      <button onclick='window.location = \"/start\"'>start</button>\n";
			html += "      <button onclick='window.location = \"/stop\"'>stop</button>\n";
			html += "    </p>\n";
			html += "    <p>status</p>\n";
			html += "    <p>Playing: " + (playbackActive ? "true" : "false") + "</p>\n";
			html += "    <p>Path: " + audioPath + "</p>\n";
			html += "    <p>Position: " + Math.round(soundChannel.position / 1000.0) + " seconds</p>\n";
			html += "    <p>Volume: " + Math.round(volume * 100.0).toString() + "%</p>\n";
			html += "    <p>\n";
			html += "      <button onclick='window.location = \"/\"'>Refresh</button><br/>\n";
			html += "    </p>\n";
			html += "  </body>\n";
			html += "</html>";
			return html;
		}
		
		private function localhostSocketData(e:ProgressEvent):void
		{
			var socket:Socket = e.target as Socket;
			var msg:String = socket.readUTFBytes(socket.bytesAvailable);
			
			var html:String = "<!DOCTYPE html>\n";
			
			trace(socket.remoteAddress + ":" + socket.remotePort + "<" + msg.substr(0, msg.indexOf("\n")));
			
			// check for http GET /
			if (msg.substr(0, 11) == "GET /hello ")
			{
				html += "<html>\n<head>\n<title>Hello HTML</title>\n</head>\n<body>\n<p>sudoPod API!</p>\n</body>\n</html>";
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 6) == "GET / ")
			{
				html += genHTML();
				
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 11) == "GET /start ")
			{
				startPlayback();
				
				html += genHTML();
				
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 10) == "GET /stop ")
			{
				stopPlayback();
				
				html += genHTML();
				
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 14) == "GET /volumeUp ")
			{
				volume += 0.1;
				
				html += genHTML();
				
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 16) == "GET /volumeDown ")
			{
				volume -= 0.1;
				
				html += genHTML();
				
				socket.writeUTFBytes(html);
				socket.flush();
			}
			else if (msg.substr(0, 17) == "GET /favicon.ico ")
			{
				// Noisebridge icon (w/counterfit header)
				var bytes:Array = [0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 
					0x20, 0x33, 0x30, 0x32, 0x20, 0x46, 0x6f, 0x75, 
					0x6e, 0x64, 0x0d, 0x0a, 0x44, 0x61, 0x74, 0x65, 
					0x3a, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x30, 
					0x39, 0x20, 0x41, 0x70, 0x72, 0x20, 0x32, 0x30, 
					0x31, 0x33, 0x20, 0x30, 0x36, 0x3a, 0x30, 0x30, 
					0x3a, 0x33, 0x38, 0x20, 0x47, 0x4d, 0x54, 0x0d, 
					0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 
					0x20, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 
					0x32, 0x2e, 0x32, 0x2e, 0x32, 0x32, 0x20, 0x28, 
					0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x29, 0x0d, 
					0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 
					0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 
					0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6e, 
					0x6f, 0x69, 0x73, 0x65, 0x62, 0x72, 0x69, 0x64, 
					0x67, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x66, 
					0x61, 0x76, 0x69, 0x63, 0x6f, 0x6e, 0x2e, 0x69, 
					0x63, 0x6f, 0x0d, 0x0a, 0x56, 0x61, 0x72, 0x79, 
					0x3a, 0x20, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 
					0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 
					0x67, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 
					0x6e, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 
					0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 
					0x70, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 
					0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 
					0x68, 0x3a, 0x20, 0x31, 0x39, 0x37, 0x0d, 0x0a, 
					0x4b, 0x65, 0x65, 0x70, 0x2d, 0x41, 0x6c, 0x69, 
					0x76, 0x65, 0x3a, 0x20, 0x74, 0x69, 0x6d, 0x65, 
					0x6f, 0x75, 0x74, 0x3d, 0x35, 0x2c, 0x20, 0x6d, 
					0x61, 0x78, 0x3d, 0x31, 0x30, 0x30, 0x0d, 0x0a, 
					0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 
					0x6f, 0x6e, 0x3a, 0x20, 0x4b, 0x65, 0x65, 0x70, 
					0x2d, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x0d, 0x0a, 
					0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 
					0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, 
					0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 
					0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 
					0x3d, 0x69, 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 
					0x39, 0x2d, 0x31, 0x0d, 0x0a, 0x0d, 0x0a, 0x1f, 
					0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
					0x03, 0x2d, 0x8e, 0x3b, 0x0f, 0xc2, 0x30, 0x0c, 
					0x84, 0xf7, 0xfe, 0x0a, 0xd3, 0x9d, 0x98, 0xc7, 
					0x86, 0x4c, 0x06, 0x68, 0x11, 0x48, 0x3c, 0x3a, 
					0x84, 0x81, 0x31, 0x10, 0x43, 0x2a, 0xd1, 0xa4, 
					0x6a, 0xd3, 0x56, 0xfc, 0x7b, 0xda, 0xc2, 0x72, 
					0xb2, 0xef, 0x6c, 0x7d, 0x47, 0x93, 0xe4, 0xb2, 
					0x55, 0xb7, 0x2c, 0x85, 0xbd, 0x3a, 0x1d, 0x21, 
					0xbb, 0x6e, 0x8e, 0x87, 0x2d, 0xc4, 0x53, 0xc4, 
					0x43, 0xaa, 0x76, 0x88, 0x89, 0x4a, 0x7e, 0xc9, 
					0x42, 0xcc, 0x10, 0xd3, 0x73, 0x2c, 0x23, 0xb2, 
					0xa1, 0x78, 0x4b, 0xb2, 0xac, 0x4d, 0xbf, 0x84, 
					0x3c, 0xbc, 0x59, 0x2e, 0x67, 0x0b, 0xd8, 0xf9, 
					0xc6, 0x19, 0xc2, 0x9f, 0x11, 0x11, 0x8e, 0x07, 
					0x74, 0xf7, 0xe6, 0x33, 0xfc, 0xcc, 0xe5, 0x3f, 
					0xef, 0xa7, 0x88, 0x4a, 0xa9, 0x2c, 0x83, 0xf1, 
					0x8f, 0xa6, 0x60, 0x17, 0xc0, 0xea, 0x1a, 0x0a, 
					0xdf, 0xb2, 0x01, 0xd2, 0x60, 0x2b, 0x7e, 0xae, 
					0x63, 0x1b, 0x42, 0x59, 0xaf, 0x10, 0xbb, 0xae, 
					0x13, 0xce, 0xe7, 0x35, 0xdf, 0xab, 0xdc, 0xbc, 
					0x58, 0x38, 0x0e, 0xf8, 0xd4, 0x6d, 0xfe, 0xf0, 
					0x4e, 0xf4, 0x12, 0x4b, 0xcb, 0x15, 0x13, 0x6a, 
					0x29, 0x08, 0xcb, 0x01, 0x3b, 0x02, 0x7b, 0xcc, 
					0x50, 0x32, 0xfa, 0x02, 0x4c, 0x97, 0xdc, 0x16, 
					0xdf, 0x00, 0x00, 0x00];
				for (var i:int = 0; i < bytes.length; i++)
				{
					socket.writeByte(bytes[i]);
				}
				socket.flush();
			}
			else
			{
				closeLocalhostSocket(socket);
			}
		}
		
		private function localhostClientClose(e:Event):void
		{
			var socket:Socket = e.target as Socket;
			closeLocalhostSocket(socket);
		}
		
		private function localhostIOError(e:IOErrorEvent):void
		{
			// TODO: Add error handling and recover
			trace("ERROR: " + e.text);
		}
		
		private function localhostClose(e:Event):void
		{
			// TODO: Add error handling and recover
			trace("localhost Server socket closed.");
		}
	}	
}