기존에 Flex3에서 HTTPService는 직접 xml을 확인하여 valueobject를 만들던지 자동으로 파싱해주는 객체를 그냥 쓰던지 그런식으로 했었는데요. Flex4에서는 쉽게 Service객체를 만들어주고, ValueObject를 만들어줍니다.

근데, 그냥 기존방식을 쓰는 게 나을 것 같아요-_- 뭔놈의 클래스를 뭐이리 만들지-_-
그래도 제공하는 기능이니까 한번 사용해서 만들어봅시다-_-

다운로드는 여기서....-_-

1. 프로젝트 생성

New -> Flex Project -> Application type은 Desktop(구글의 httpservice를 가져올 것이어서 데탑으로-_-) -> Finish.

2. 서비스생성
구글에서 날씨를 가져오는 Service를 추가해봅시다.
아래 Data/Services탭에서 Connect to Data/Service..클릭.
HTTP선택 -> Opearation 첫번째 항목에 getWeather로 변경. url은 http://www.google.co.kr/ig/api?weather=seoul 입력.
url만 입력하면 ?붙은 파라메터는 자동으로 Parameters탭에 들어갑니다. weather가 들어가는데, 캐쉬문제 때무넹 캐쉬파라메터도 하나 추가합니다.
Add누르고, cache입력하고, DataType은 Number.
Service name은 WeatherService로 하죠. Finish.

여기서 이름 네이밍룰에 주의해야하는데, Service name이랑 parameter name이랑 같은 게 있으면 안됩니다. 에러나더군요.

3. 서비스에 대한 리턴타입 지정
이제 리턴타입을 지정해줘야하는데요.
그전에 데이터를 잘 가져오는지 테스트를 해볼 수 있습니다.
방금 생성한 WeatherService의 getWeather를 선택하고 오른쪽버튼 누르면 Configure Return Type이 있습니다.
이거 선택하기전에 Test Opearation이라고 해서 테스트를 해볼 수 있는 게 있네요.
테스트를 쉽게 해볼 수 있어서 참 좋네요. Test를 누르면 데이터를 가져와서 Tree View 또는 Raw View로 볼 수 있습니다.
사용자 삽입 이미지
근데, 이상한 점을 발견했는데, 네이버오픈API나 몇몇 XML은 이걸로 하면 잘 안돼요. 왜그런지 모르겠네요. 이런 에러메세지를 뿜습니다.
InvocationTargetException:The response is not a valid XML or a JSON string
이거 왜그런지 아시는 분은 답변좀....굽신굽신....-_-

암튼, 날씨API는 되니까 해봅시다-_-
Configure Return Type선택 후, Auto-detect the return type from sample data선택. 뭐 그냥 object로 받아도 되는데, 필요한 데이터만 가져온다고 하면 쉽게 valueobject를 만들 수 있는 장점이 있어요.
파라메터 방식을 선택하고, seoul을 입력합니다.
그러면 결과값이 나오는데, 여기서 필요한 건 현재 날씨이기 때문에 select root에서 current_conditions를 선택합니다. 그리고, Finish를 누르면 valueObject가 만들어지는데, 한개의 node마다 클래스가 2개씩 생겨요-_-

4. UI생성
UI를 만들어봅시다.
[code]<s:Button id="btnClose" label="X"  width="29" x="67" y="1" click="btnClose_clickHandler(event)"/>
<mx:Image x="6" y="32" width="40" height="40" id="imgIcon"/>
<s:Label x="0" y="80" width="100" textAlign="center"  id="labelCondition" />
<s:Label x="59" y="45" text="30º" fontSize="20" id="labelTemp"/>[/code]
초간단 UI...UI라고 불리지도 못하는 UI.

5. 데이터 바인딩
이제 데이터를 불러와야해요.
Design모드로 가서 아무라벨이나 붙잡고, 오른쪽버튼 눌러서 Bind To Data를 선택. 그리고 방금 만든 서비스를 바인딩해요. 전 labelCondition을 선택했는데, 그러면 WeatherService, getWeather의 condition을 선택합니다.
그러면 service태그도 알아서 들어가고 해당 라벨에는 creationComplete이벤트핸들러가 추가되었네요.
getWeather파라메터는 "seoul"과 cache는 랜덤값을 넣어줍니다.
그리고 실제 태그에는 condition.@data에 값이 있기 때문에, label의 text부분에 getWeatherResult.lastResult.condition.data로 변경해주시면 됩니다.
[code]protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
{
    getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
}[/code]

6. 완성된 코드
그럼 1분마다 데이터를 가져오는 타이머도 만들고 윈도우틀도 없애서 위젯형태로 만들어버리고, 나머지 label에도 값에 맞게 바인딩 시키면 완성이 됩니다.
[code]<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       xmlns:weatherservice="services.weatherservice.*"
                       width="150" height="150"
                       applicationComplete="windowedapplication_applicationCompleteHandler(event)"
                       showStatusBar="false"
                       backgroundColor="#48FF00">
    <fx:Script>
        <![CDATA[
            import flash.events.MouseEvent;
            import flash.events.TimerEvent;
            import flash.utils.Timer;
           
            import mx.controls.Alert;
            import mx.events.FlexEvent;
           
            import spark.components.mediaClasses.VolumeBar;
           
            private var timer:Timer;
           
            protected function windowedapplication_applicationCompleteHandler(event:FlexEvent):void
            {
                timer = new Timer(1000, 0);
                timer.addEventListener(TimerEvent.TIMER, timerHandler);
                timer.start();
            }
           
            protected function btnClose_clickHandler(event:MouseEvent):void
            {
                nativeWindow.close();
            }

            protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
            {
                requestData();
            }

            protected function labelTitle_mouseDownHandler(event:MouseEvent):void
            {
                nativeWindow.startMove();
            }
           
            protected function timerHandler(event:TimerEvent):void
            {
                requestData();               
            }
           
            private function requestData():void
            {
                getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:CallResponder id="getWeatherResult"/>
        <weatherservice:WeatherService id="weatherService" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"/>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:Label id="labelTitle" text="서울날씨"  x="0" y="0" height="22" width="121" verticalAlign="middle" mouseDown="labelTitle_mouseDownHandler(event)"/>
    <s:Button id="btnClose" label="X"  width="30" x="120" y="1" click="btnClose_clickHandler(event)"/>
    <mx:Image x="25" y="47" width="40" height="40" id="imgIcon" source="http://www.google.co.kr{getWeatherResult.lastResult.icon.data}"/>
    <s:Label x="2" y="114" width="148" textAlign="center"  id="labelCondition"  creationComplete="labelCondition_creationCompleteHandler(event)" text="{getWeatherResult.lastResult.condition.data}"/>
    <s:Label x="92" y="58" fontSize="20" id="labelTemp" text="{getWeatherResult.lastResult.temp_c.data}ºC"/>
</s:WindowedApplication>
[/code]

7. Network Monitor로 데이터 확인
HTTPService 요청이 제대로 갔는지 확인할 수 있는 Network Monitor 기능이 추가되었습니다. 기존에는 확인하려면 직접 그냥 노가다로 해보거나 와이어샤크나 firebug 등의 http watcher를 이용해서 했었죠. 근데 이제 Flash Builder에 포함되어져있네요. 그냥 모니터링 하는거라 조금만 만져보면 알 수 있네요. row데이터로 볼 수도 있고, hex데이터로도 볼 수 있고 그러네요. 근데 한글이 깨지네-_-
사용자 삽입 이미지
사용자 삽입 이미지


 
Posted by 머드초보
,
 
문제가 된다면 삭제하겠습니다-_-

전에 WireShark를 이용해서 알송패킷을 캡쳐했을 때 보면 특정주소의 WebService를 요청해서 가져오게 되어있었습니다. http://mudchobo.tomeii.com/tt/434

실제 전송하는 데이터는 MD5값인데, 이게 어떻게 생성되는지 몰라서 구글에서 검색을 하니 어떤 블로그에서 시도한 흔적을 발견했습니다. 거기에 있는 댓글에서 발견했습니다^^
http://dialup.egloos.com/152001

"MP3 파일의 경우 strChecksum 값은 ID3태그등을 제외한 순수 MP3 음악 데이터를 앞에서부터 163840 바이트 읽어서 MD5로 돌린 값입니다. " 라고 친절하게 답변이....-_-

그래서 이렇게 하니까 잘 되더라구요. 아래는 샘플 코드입니다.
필요한 라이브러리는 MD5생성해주는 corelib가 필요합니다.
http://code.google.com/p/as3corelib/
[code]<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    applicationComplete="applicationCompleteHandler()">
    <mx:Script>
        <![CDATA[
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
            import flash.text.engine.ContentElement;
            import mx.messaging.messages.HTTPRequestMessage;
            import mx.rpc.http.HTTPService;
            import com.adobe.crypto.MD5;
            private function applicationCompleteHandler():void
            {
                var fs:FileStream = new FileStream();
                fs.open(new File("C:/Users/mudchobo/Downloads/임재범 - 사랑이라서.mp3"), FileMode.READ);
                var fileSize:int = fs.bytesAvailable;
               
                for (var i:int = 0; i < 500000; i++)
                {
                    if (fs.bytesAvailable >= 3 && fs.readUTFBytes(3) == "ID3")
                    {
                        var sizeByte:ByteArray = new ByteArray();
                       
                        // ID3v2길이구하기
                        fs.position += 3;
                        fs.readBytes(sizeByte, 0, 4);
                        var id3Size:int = sizeByte[0] << 21 | sizeByte[1] << 14 | sizeByte[2] << 7 | sizeByte[3];
                        fs.position = id3Size + 10;
                        break;
                    }
                }
               
                // ID3태그 없는 경우
                if (i == 500000)
                {
                    fs.position = 0;
                }
               
                // 공백있는 ID3태그에 대한 처리
                for (i = 0; i < 50000; i++)
                {
                    if (fs.readUnsignedByte() == 255)
                    {
                        var a:int = fs.readUnsignedByte();
                        if ((a >> 5) == 7)
                        {
                            fs.position += -2;
                            break;
                        }
                    }
                }
               
                // 163840만큼 읽어서 md5생성
                var data:ByteArray = new ByteArray();
                fs.readBytes(data, 0, 163840);
                var md5:String = MD5.hashBytes(data);
               
                // 웹서비스 요청
                var httpService:HTTPService = new HTTPService();
                httpService.method = HTTPRequestMessage.POST_METHOD;
                httpService.contentType = "application/soap+xml";
                httpService.url = "http://lyrics.alsong.co.kr/alsongwebservice/service1.asmx";
                httpService.request = new XML('<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="ALSongWebServer/Service1Soap" xmlns:ns1="ALSongWebServer" xmlns:ns3="ALSongWebServer/Service1Soap12"><SOAP-ENV:Body><ns1:GetLyric5><ns1:stQuery><ns1:strChecksum>' + md5 + '</ns1:strChecksum><ns1:strVersion>2.0 beta2</ns1:strVersion><ns1:strMACAddress>ffffffffffff</ns1:strMACAddress><ns1:strIPAddress>255.255.255.0</ns1:strIPAddress></ns1:stQuery></ns1:GetLyric5></SOAP-ENV:Body></SOAP-ENV:Envelope>');
                httpService.addEventListener(ResultEvent.RESULT, resultHandler);
                httpService.addEventListener(FaultEvent.FAULT, faultHandler);
                httpService.send();
            }
           
            private function resultHandler(event:ResultEvent):void
            {
                trace("싱크가사 = " + event.result.Envelope.Body.GetLyric5Response.GetLyric5Result.strLyric);
            }
           
            private function faultHandler(event:FaultEvent):void
            {
                trace(event.toString());
            }
           
        ]]>
    </mx:Script>
</mx:WindowedApplication>
[/code]
여기서 알송가사에서 제공하는 웹서비스는 WebService인데, HTTPService로 요청한 이유는 wsdl문서가 어디있는지 못찾겠음 ㄷㄷ 그래서 그냥 httpservice로 xml통채로 넘겨버리니-_- 되더라구요 ^^
xml넘길 때 strCheckSum값만 md5구한걸로 넘기면 돼요^^

[code]싱크가사 = [00:00.00]하루 하루 그대만 보여서<br>[00:23.28]매일매일 눈을 가리고 살아<br>[00:31.34]그대 곁을 나조차도 모르게 머물며<br>[00:38.91]메마른 내 가슴이 그댈 잊어버리지 못한 이유는<br>[00:00.00]<br>[00:00.00]<br>[00:49.93]사랑이라서 사랑이라서<br>[00:56.06]두번다시 못볼 사랑이라서<br>[00:00.00]<br>[01:03.32]하늘이 하는 일 돌릴 수 없는 일<br>[01:11.39]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[01:19.51]죽어서 보라고 그래서 보라고<br>[01:26.42]그때라도 사랑한 마음이 남아있게된다면<br>[01:34.53]그때쯤에<br>[00:00.00]<br>[01:42.93]어딜가도 그대만 보여서<br>[01:49.88]매일매일 나를 지우고 살아<br>[01:57.28]한순간도 바람처럼 떠날줄 모르고<br>[02:04.71]죽어도 내가슴이 그댈 떠나보내지 못한 이유는<br>[00:00.00]<br>[02:15.49]사랑이라서 사랑이라서<br>[02:23.68]가슴가득 맺힌 사랑이라서<br>[00:00.00]<br>[02:29.68]하늘이 하는 일 돌릴 수 없는 일<br>[02:37.81]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[02:45.79]죽어서 보라고 그래서 보라고<br>[02:52.51]그때라도 사랑한 마음이 남아있게 된다면<br>[03:00.90]그때쯤에<br>[00:00.00]<br>[03:04.66]간절히 바랬어 너에게 가는일<br>[00:00.00]<br>[03:33.19]하늘이 하는 일 돌릴 수 없는 일<br>[03:41.80]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[03:49.23]죽어서 보라고 그래서 보라고<br>[03:56.56]그때라도 사랑한 마음이 남아있게된다면<br>[04:04.29]그때쯤에<br>[/code]

PS. HTTPService에서 xml전송해서 하는 거 어떻게 하는지 몰랐는데, 그냥 httpservice에 있는 request객체에 xml을 생성해서 넣어주면 된다는....-_-

 
Posted by 머드초보
,
 
ActionScript3가 제공하는 Sound클래스에서 구하는 재생시간은 구하는데 너무 오래걸립니다.
Sound객체를 생성에 Complete이벤트가 발생한 다음에야 재생시간을 구할 수 있습니다.
재생목록에 추가를 해서 그냥 간단히 재생시간을 보여줘야하는데, 100곡을 재생목록에 추가를 해버리면 AIR애플리케이션이 미친듯이 메모리를 잡아먹는 것을 볼 수 있습니다-_-;

그래서 찾아보니, mp3 Header정보를 이용해서 재생시간을 구할 수 있습니다.

재생시간 = 파일크기 * 8 / 비트레이트 로 구할 수 있습니다.
그러면 비트레이트만 구하면 되는데, 이건 MP3Header에서 찾을 수 있습니다.

MP3 BITRATE는 http://www.datavoyage.com/mpgscript/mpeghdr.htm에 의하면 MPEG버전, LAYER, Bitrate Index로 구할 수 있습니다. MP3는 각각 Frame별로 Header가 존재하는데, 거기서 위에 정보를 구할 수 있는 것 같습니다.

ID3v2태그가 있는 경우는 ID3v2태그 다음에 MP3Header가 나옵니다. 그렇다면 ID3v2태그의 길이를 구해서 그 다음부터 MP3Header를 찾아야 합니다. ID3v2태그는 길이가 가변적입니다.그래서 총길이를 알아야하는데, 총길이는 ID3v2태그 맨 앞에 나오는 10byte Header부분에서 구할 수 있습니다.

이 Header부분의 6byte~10byte까지가 ID3v2의 총 길이인데요. 여기의 값이 00 00 1F 76(00011111 01110110)이라면 각각 MSB를 제거하여 붙인 값이 총 길이가 된다더군요. 00111111110110 -> 4086byte.

우선 C#으로 구현해놓은 소스가 있습니다. 그것을 Actionscript3로 변환했습니다(구현하려니 힘들어서 ㅠ)
C#소스 -> http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=79

제가 변환한 AS3용 MP3Header클래스입니다. 제가 가지고 있는 MP3 대부분 테스트해봤는데 잘 되더라구요.
invalid-file

MP3Header클래스


사용법은 이렇게 쓰시면 됩니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import util.MP3Header;
            private function init():void
            {
                var mp3Header:MP3Header = new MP3Header();
                mp3Header.readMP3Information("D:/눈물이 글썽 - 서진영.MP3");
                trace("BITRATE = " + mp3Header.intBitRate);
                trace("Frequency = " + mp3Header.intFrequency);
                trace("Mode = " + mp3Header.strMode);
                trace("LengthFormatted = " + mp3Header.strLengthFormatted);
                trace("Length = " + mp3Header.intLength);
            }
        ]]>
    </mx:Script>   
</mx:WindowedApplication>
[/code]
[code]
BITRATE = 64
Frequency = 44100
Mode = Stereo
LengthFormatted = 04:00
Length = 240
[/code]

 
Posted by 머드초보
,
 
다...다른 방법을 검색해서 구했습니다-_-;
하지만, 이건 한글은 이제 잘 읽는 것 같은데......한자를 못 읽습니다-_-; 원래 안되는건가....-_-;

우선 ID3Parser 링크입니다. 매우 빠른 속도로 ID3데이터를 가져옵니다.
http://blog.ashier.com/2007/11/08/id3-parser/

여기서 한글을 읽을 수 있게 수정하는 부분이......
보면 ID3데이터를 추출해오는 부분이 있습니다.
거기서 한글로 추출할 수 있게 변경합니다.

[code]
private function parseFrames():void {
    var id:String = "";
    var size:uint = 0;
    if(fs.position <length) {
        try {
            id = fs.readUTFBytes(frameIdSize);
            size = fs.readUnsignedInt();
            if (version>= 3) {
                fs.readByte();
                fs.readByte();
            }
            if(id.match(regEx)) {
                var obj:Object = new Object();
                obj.encoding = fs.readByte();
                obj.text = fs.readUTFBytes(size - 1);
                frames[id] = obj;
            }
            parseFrames();
        }catch(e:Error) {}
    }
}
[/code]
이 부분이 있는데요. 한글을 읽어오는 readUTFBytes 부분을 바꿔줍니다.
[code]
//obj.text = fs.readUTFBytes(size - 1);
if (obj.encoding == "1")
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "unicode"));
}
else
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "EUC-KR"));
}
[/code]
보니까 obj.encoding이 1인 값은 unicode로 인코딩 된 것 같아요. obj.encoding이 0인 값은 EUC-KR로...
이렇게 하니까 잘 되네요....가끔 공백을 추출해오고 그래서 trim처리했습니다.

아래글은 ID3v1방식 추출 방법 및 ID3v2의 다른 추출 방식 입니다^^ 참고하세요~
http://mudchobo.tomeii.com/tt/356
 
Posted by 머드초보
,
 
그......sound객체를 이용해서 하는 것이라니라 직접 바이트로 읽어서-_-; 하는 법이 있더라구요.
우선 ID3v1태그는 음악내용 맨 뒤에 있습니다. 그래서 추출하기는 쉽습니다.

http://cafe.naver.com/flexcomponent.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=10087
flexcomponent에서 혀니님께서 작성하신 글입니다.

그 다음 ID3v2태그는 맨 앞에 있습니다. 또한 이것을 추출하는 방법이 만만치 않습니다-_-;
왜냐하면 이건 뭐 길이도 가변적이고, 태그가 복잡합니다-_-;
그래서 태그를 공부하는 것보다 누가 만들어 놓은 것을 찾는 게 더 빠를 듯하여.....-_-;
구글신님께 부탁드리니 잘 찾아주셨습니다 ^^

http://blog.benstucki.net/?id=3

이것을 그대로 사용하시면 한글이 깨집니다.
사용자 삽입 이미지

저 위에 소스를 다운 받아서 첫번째 ID3v1에서 사용한 방법을 사용합니다.
ID3Reader.as파일에 readTextFrame이라는 함수가 있는데요.
이게 텍스트를 읽어올 때 사용하는 함수인 것 같습니다.
[code]
//obj.text = bytes.readUTFBytes(size-1);
obj.text = bytes.readMultiByte(size-1, "EUC-KR");
[/code]
요렇게 바꿔줍니다-_-;
그런 다음에 실행하면 한글이 잘 나옵니다.
사용자 삽입 이미지
하지만........-_-;
기존에 Actionscript에서 제공하는 Sound클래스에서 추출하는 ID3에서 잘나오는 한글(즉 UTF-8로 인코딩 된 것)은 여기서 읽으면 잘 안나옵니다-_-; 이거 추출하는 프로그램 만드신 분이 잘못 만든건지 잘 모르겠는데요. 확인해봐야할 것 같네요 ㅠ

사용자 삽입 이미지
헉...-_-; 인코딩을 unicode로 바꾸니까 잘 나오네요. 기존에 Actionscript에서 잘 나오는 것은 unicode로 되어있어서 그런거였나요? ㅠ 더 확인해봐야겠습니다 ㅠ
이거 말고, 요아래 것으로 하면 잘 되네요.

ID3v2추출하는 다른 방법입니다.
http://mudchobo.tomeii.com/tt/357
 
Posted by 머드초보
,