bug#210212 (initial commit)
diff --git a/plugins/org.eclipse.actf.ai.audio.description/.classpath b/plugins/org.eclipse.actf.ai.audio.description/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.actf.ai.audio.description/.project b/plugins/org.eclipse.actf.ai.audio.description/.project
new file mode 100644
index 0000000..f0c20b7
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.actf.ai.audio.description</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.actf.ai.audio.description/META-INF/MANIFEST.MF b/plugins/org.eclipse.actf.ai.audio.description/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e62719a
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/META-INF/MANIFEST.MF
@@ -0,0 +1,20 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Eclipse ACTF Audio Description Plug-in (Incubation)
+Bundle-SymbolicName: org.eclipse.actf.ai.audio.description;singleton:=true
+Bundle-Version: 0.0.1
+Bundle-Activator: org.eclipse.actf.ai.audio.description.DescriptionPlugin
+Bundle-Vendor: Eclipse.org
+Bundle-Localization: plugin
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.actf.ai.fennec,
+ org.eclipse.actf.common,
+ org.eclipse.actf.ai.navigator,
+ org.eclipse.actf.ai.voice,
+ org.eclipse.actf.ai.xmlstore
+Eclipse-LazyStart: true
+Export-Package: org.eclipse.actf.ai.audio.description,
+ org.eclipse.actf.ai.audio.description.impl,
+ org.eclipse.actf.ai.audio.description.util,
+ org.eclipse.actf.ai.audio.description.views
diff --git a/plugins/org.eclipse.actf.ai.audio.description/about.html b/plugins/org.eclipse.actf.ai.audio.description/about.html
new file mode 100644
index 0000000..481dbcf
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/about.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 5, 2006</p>	
+<h3>License</h3>
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  
+Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/org/documents/epl-v10.php">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor&rsquo;s license 
+that was provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.actf.ai.audio.description/build.properties b/plugins/org.eclipse.actf.ai.audio.description/build.properties
new file mode 100644
index 0000000..86eff37
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/build.properties
@@ -0,0 +1,21 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml,\
+               plugin.properties,\
+               plugin_ja.properties,\
+               icons/,\
+			   about.html,\
+               src/messages.properties,\
+               src/messages_ja.properties
diff --git a/plugins/org.eclipse.actf.ai.audio.description/icons/micx16.gif b/plugins/org.eclipse.actf.ai.audio.description/icons/micx16.gif
new file mode 100644
index 0000000..f60045c
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/icons/micx16.gif
Binary files differ
diff --git a/plugins/org.eclipse.actf.ai.audio.description/icons/micx48.gif b/plugins/org.eclipse.actf.ai.audio.description/icons/micx48.gif
new file mode 100644
index 0000000..3b79303
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/icons/micx48.gif
Binary files differ
diff --git a/plugins/org.eclipse.actf.ai.audio.description/plugin.properties b/plugins/org.eclipse.actf.ai.audio.description/plugin.properties
new file mode 100644
index 0000000..35d412b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/plugin.properties
@@ -0,0 +1,12 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+AudioDescription.preference.name=Audio Description
+AudioDescription.view.name=Audio Description
diff --git a/plugins/org.eclipse.actf.ai.audio.description/plugin.xml b/plugins/org.eclipse.actf.ai.audio.description/plugin.xml
new file mode 100644
index 0000000..9b67bb3
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/plugin.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+   <extension
+         point="org.eclipse.actf.ai.navigator.MediaControl">
+      <controller class="org.eclipse.actf.ai.audio.description.impl.MediaControlImpl"/>
+   </extension>
+   <extension
+         point="org.eclipse.ui.views">
+      <view
+            class="org.eclipse.actf.ai.audio.description.views.DescriptionView"
+            icon="icons/micx16.gif"
+            id="org.eclipse.actf.ai.audio.description.views.DescriptionView"
+            name="%AudioDescription.view.name"/>
+   </extension>
+   <extension
+         point="org.eclipse.ui.perspectiveExtensions">
+      <perspectiveExtension targetID="org.eclipse.actf.examples.aibrowser.DefaultPerspective">
+         <view
+               closeable="false"
+               id="org.eclipse.actf.ai.audio.description.views.DescriptionView"
+               moveable="true"
+               ratio="0.7"
+               relationship="bottom"
+               relative="org.eclipse.actf.ai.navigator.views.NavigatorTreeView"
+               showTitle="true"
+               standalone="false"
+               visible="true"/>
+      </perspectiveExtension>
+   </extension>
+   <!--extension
+         point="org.eclipse.ui.actionSets">
+      <actionSet
+            id="org.eclipse.actf.ai.audio.description.actionSet1"
+            label="org.eclipse.actf.ai.audio.description.actionSet1"
+            visible="true">
+         <action
+               class="org.eclipse.actf.ai.audio.description.actions.AudioDescriptionActionDelegate"
+               id="org.eclipse.actf.ai.audio.description.AudioDescription"
+               label="&amp;Audio Description"
+               menubarPath="org.eclipse.actf.examples.aibrowser.FileMenu/filegroup"
+               state="false"
+               style="toggle"
+               tooltip="Toggle audio description mode"/>
+      </actionSet>
+   </extension-->
+   <extension
+         point="org.eclipse.ui.preferencePages">
+      <page
+            class="org.eclipse.actf.ai.audio.description.preferences.ADPreferencePage"
+            id="org.eclipse.actf.ai.audio.description.preferences.ADPreferencePage"
+            name="%AudioDescription.preference.name"/>
+   </extension>
+   <extension
+         point="org.eclipse.core.runtime.preferences">
+      <initializer class="org.eclipse.actf.ai.audio.description.preferences.ADPreferenceInitializer"/>
+   </extension>
+
+</plugin>
diff --git a/plugins/org.eclipse.actf.ai.audio.description/plugin_ja.properties b/plugins/org.eclipse.actf.ai.audio.description/plugin_ja.properties
new file mode 100644
index 0000000..db0b66b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/plugin_ja.properties
@@ -0,0 +1,12 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+AudioDescription.preference.name=\u97f3\u58f0\u89e3\u8aac
+AudioDescription.view.name=\u97f3\u58f0\u89e3\u8aac
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/messages.properties b/plugins/org.eclipse.actf.ai.audio.description/src/messages.properties
new file mode 100644
index 0000000..86b5145
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/messages.properties
@@ -0,0 +1,21 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+AudioDescription.view.time=Time
+AudioDescription.view.desc=Description
+AudioDescription.view.enable=Enable
+AudioDescription.view.notEnable=Not Enable
+
+AudioDescription.preference.title=Audio Description Preference
+AudioDescription.voice.engine=TTS engine
+Metadata.available=Audio Description is Available
+
+AudioDescription.on=Audio Description is on
+AudioDescription.off=Audio Description is off
\ No newline at end of file
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/messages_ja.properties b/plugins/org.eclipse.actf.ai.audio.description/src/messages_ja.properties
new file mode 100644
index 0000000..c0b1c63
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/messages_ja.properties
@@ -0,0 +1,22 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+AudioDescription.view.time=\u6642\u9593
+AudioDescription.view.desc=\u8aac\u660e
+AudioDescription.view.enable=\u89e3\u8aac\u30aa\u30f3
+AudioDescription.view.notEnable=\u89e3\u8aac\u30aa\u30d5
+
+AudioDescription.preference.title=\u97f3\u58f0\u8a2d\u5b9a
+AudioDescription.voice.engine=TTS\u30a8\u30f3\u30b8\u30f3
+
+Metadata.available=\u97f3\u58f0\u89e3\u8aac\u5229\u7528\u53ef\u80fd
+
+AudioDescription.on=\u97f3\u58f0\u89e3\u8aac\u30aa\u30f3
+AudioDescription.off=\u97f3\u58f0\u89e3\u8aac\u30aa\u30d5
\ No newline at end of file
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/DescriptionPlugin.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/DescriptionPlugin.java
new file mode 100644
index 0000000..317860e
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/DescriptionPlugin.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.description;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Locale;
+
+import org.eclipse.actf.ai.audio.description.impl.MetadataManager;
+import org.eclipse.actf.ai.audio.description.impl.MetadataProviderImpl;
+import org.eclipse.actf.ai.audio.description.views.DescriptionView;
+import org.eclipse.actf.ai.navigator.IMediaControl;
+import org.eclipse.actf.ai.tts.ITTSEngine;
+import org.eclipse.actf.ai.voice.internal.AbstractPreferenceUIPlugin;
+import org.eclipse.actf.ai.voice.internal.TTSRegistry;
+import org.eclipse.actf.ai.xmlstore.IXMLInfo;
+import org.eclipse.actf.ai.xmlstore.IXMLSelector;
+import org.eclipse.actf.ai.xmlstore.IXMLStore;
+import org.eclipse.actf.ai.xmlstore.IXMLStoreService;
+import org.eclipse.actf.ai.xmlstore.XMLStorePlugin;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.osgi.framework.BundleContext;
+
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class DescriptionPlugin extends AbstractPreferenceUIPlugin implements IPropertyChangeListener {
+
+    // The plug-in ID
+    public static final String PLUGIN_ID = "org.eclipse.actf.ai.audio.description";
+
+    public static final String PREF_ENGINE = "AudioDescriptionTTSEngine";
+
+    // The shared instance
+    private static DescriptionPlugin plugin;
+
+    private boolean enable = false;
+
+    private MetadataManager activeManager;
+
+    ITTSEngine engine;
+
+    /**
+     * The constructor
+     */
+    public DescriptionPlugin() {
+        plugin = this;
+    }
+
+    public IMetadataProvider getMetadata(String url) {
+        IXMLStoreService service = XMLStorePlugin.getDefault().getXMLStoreService();
+        IXMLStore store = service.getRootStore();
+        IXMLSelector selector1 = service.getSelectorWithDocElem("puits", "urn:puits");
+        IXMLSelector selector2 = service.getSelectorWithIRI(url);
+        store = store.specify(selector1);
+        store = store.specify(selector2);
+        
+        ArrayList<IXMLInfo> list = new ArrayList<IXMLInfo>();
+        for(Iterator<IXMLInfo> i = store.getInfoIterator(); i.hasNext();){
+            list.add(i.next());
+        }
+        if(list.size() == 0)
+            return null;
+        MetadataProviderImpl provider = new MetadataProviderImpl(list);
+        if (provider != null){
+            provider.reload();
+            provider.setLocale(Locale.getDefault());
+            provider.getAlternatives();
+        }
+        return provider;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+     */
+    @Override
+    public void start(BundleContext context) throws Exception {
+        super.start(context);
+        initialize();
+    }
+
+    private void initialize() {
+        engine = newTTSEngine();
+        DescriptionPlugin.getDefault().addPropertyChangeListener(this);
+    }
+
+    private ITTSEngine newTTSEngine() {
+        ITTSEngine engine;
+        String e = DescriptionPlugin.getDefault().getPreferenceStore().getString(DescriptionPlugin.PREF_ENGINE);
+        
+        // TODO  
+        if (e.equals("org.eclipse.actf.ai.screenreader.jaws"))
+            return null;
+        engine = TTSRegistry.createTTSEngine(e);
+        if(engine != null)
+            engine.setSpeed(50);
+        return engine;
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent event) {
+        if (DescriptionPlugin.PREF_ENGINE.equals(event.getProperty())) {
+            if (null != engine) {
+                engine.stop();
+                engine.dispose();
+                engine = newTTSEngine();
+            }
+        }
+    }
+
+    public ITTSEngine getTTSEngine() {
+        return engine;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+     */
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        plugin = null;
+        super.stop(context);
+    }
+
+    /**
+     * Returns the shared instance
+     * 
+     * @return the shared instance
+     */
+    public static DescriptionPlugin getDefault() {
+        return plugin;
+    }
+
+    DescriptionView view;
+
+    public void setDescriptionView(DescriptionView view) {
+        this.view = view;
+    }
+
+    public DescriptionView getDescriptionView() {
+        return view;
+    }
+
+    public int setEnable(boolean flag) {
+        if(!isAvailable()){
+            enable = false;
+            return IMediaControl.STATUS_NOT_AVAILABLE;
+        }
+
+        enable = flag;
+        getDescriptionView().setEnable(enable);
+        if (enable) {
+            return IMediaControl.STATUS_ON;
+        } else {
+            return IMediaControl.STATUS_OFF;
+        }
+    }
+
+    public boolean isAvailable() {
+        if (activeManager == null)
+            return false;
+        return activeManager.hasMetadata();
+    }
+    
+    public boolean canSpeak() {
+        return engine != null;
+    }
+
+    public void speak(String str) {
+        if(engine != null)
+            engine.speak(str, ITTSEngine.TTSFLAG_FLUSH, -1);
+    }
+
+    public int toggleEnable() {
+        return setEnable(!getEnable());
+    }
+
+    public boolean getEnable() {
+        return enable;
+    }
+
+    public void setActiveMetadata(MetadataManager manager) {
+        this.activeManager = manager;
+    }
+    
+    public static String getString(String key) {
+        return Platform.getResourceString(getDefault().getBundle(), key);
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadata.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadata.java
new file mode 100644
index 0000000..73b69c7
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadata.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description;
+
+
+
+
+public interface IMetadata {
+    int IMPORTANCE_LOW = 0;
+    int IMPORTANCE_MIDDLE = 1;
+    int IMPORTANCE_HIGH = 2;
+    
+    int MASK_NONE = 0;
+    int MASK_PAUSE_BEFORE = 1;
+    int MASK_SPEAK = 2;
+    int MASK_PAUSE_AFTER = 4;
+    
+    public String getDescription();
+    public int getStartTime();
+    public int getDuration();
+    public String getLang();
+    public int getType();
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadataProvider.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadataProvider.java
new file mode 100644
index 0000000..2ec27de
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/IMetadataProvider.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.description;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public interface IMetadataProvider {
+    public int getSize();
+
+    public IMetadata getItem(int index);
+
+    public ArrayList<IMetadata> getAllItems();
+
+    public int getIndex(int position);
+
+    public void setLocale(Locale locale);
+    
+    public int getAlternatives();
+
+    public void setAlternative(int index);
+
+    public void reload();
+    
+    public boolean hasMetadata();
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/Messages.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/Messages.java
new file mode 100644
index 0000000..4da2611
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/Messages.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Takashi ITOH - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+
+
+public class Messages {
+    private static final String BUNDLE_NAME = "messages"; //$NON-NLS-1$
+
+    private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
+
+    private Messages() {
+    }
+
+    public static String getString(String key) {
+        // TODO Auto-generated method stub
+        try {
+            return RESOURCE_BUNDLE.getString(key);
+        } catch (MissingResourceException e) {
+            return '!' + key + '!';
+        }
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaControlImpl.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaControlImpl.java
new file mode 100644
index 0000000..49be20b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaControlImpl.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.impl;
+
+import java.util.HashMap;
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.audio.description.IMetadataProvider;
+import org.eclipse.actf.ai.audio.description.Messages;
+import org.eclipse.actf.ai.navigator.IMediaControl;
+
+
+
+
+public class MediaControlImpl implements IMediaControl {
+
+    private HashMap<IHandle, MetadataManager> map = new HashMap<IHandle, MetadataManager>();
+    //private List<MetadataManager> managers = new Vector<MetadataManager>();
+    
+    MetadataManager manager;
+    MetadataManager oldManager;
+    
+    IHandle old = null;
+    
+    IHandle handle;
+
+    public void dispose(IHandle handle) {
+    }
+
+    public void start(IHandle handle) {
+        //if (handle.getSoundControl() == null || handle.getVideoControl() == null)
+            //return;
+        initialize(handle);
+    }
+
+    private void initialize(IHandle handle) {
+        this.handle = handle;
+        MetadataManager temp = map.get(handle);
+        
+        if(temp == null){
+            String url = handle.getWebBrowser().getURL();
+            IMetadataProvider provider = DescriptionPlugin.getDefault().getMetadata(url);
+            temp = new MetadataManager(handle, provider);
+            
+            if(handle.getVideoControl() != null)
+                handle.getVideoControl().addEventListener(new MediaSyncEventListener(handle, temp));
+            //System.out.println(temp);
+            //map.put(handle, temp);
+        }
+        if(manager != null){
+            manager.stop();
+        }
+        manager = temp;
+        manager.start();
+        DescriptionPlugin.getDefault().setActiveMetadata(manager);
+        
+        if(manager.hasMetadata()){
+            if(oldManager != null){
+                System.out.println(oldManager.getMetadataProvider());
+                System.out.println(manager.getMetadataProvider());
+            }
+            if(oldManager == null || oldManager.getMetadataProvider() != manager.getMetadataProvider()){
+                if(handle.getVoice() != null){
+                    //speakInfo(false);
+                }
+                oldManager = manager;
+            }
+        }else{
+            //handle.getVoice().speak(Messages.getString("Metadata.notAvailable"), false);
+        }
+    }
+    
+    public void speakInfo(boolean flush) {
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        if (!plugin.isAvailable())
+            return;
+        if (handle.getVoice() == null)
+            return;
+        
+        if (plugin.getEnable()) {
+            handle.getVoice().speak(Messages.getString("AudioDescription.on"), flush);
+        } else {
+            handle.getVoice().speak(Messages.getString("AudioDescription.off"), flush);
+        }
+    }
+
+    public int toggleEnable() {
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        return plugin.toggleEnable();
+    }
+    
+    public boolean isAvailable(){
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        return plugin.isAvailable();
+    }
+
+    public boolean toggleViewShowing() {
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        return plugin.getDescriptionView().toggleViewShowing();
+    }
+
+    public boolean isEnabled() {
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        return plugin.getEnable();
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaSyncEventListener.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaSyncEventListener.java
new file mode 100644
index 0000000..6b50620
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MediaSyncEventListener.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.impl;
+
+import org.eclipse.actf.ai.fennec.treemanager.IMediaSyncEventListener;
+import org.eclipse.actf.ai.navigator.IMediaControl.IHandle;
+
+
+
+
+public class MediaSyncEventListener implements IMediaSyncEventListener {
+
+    private static final double EVENT_INTERVAL = 0.1; // 100ms
+    
+    private IHandle handle;
+    private MetadataManager manager;
+    
+    public MediaSyncEventListener(IHandle handle, MetadataManager manager) {
+        this.handle = handle;
+        this.manager = manager;
+    }
+
+    public void run(){
+        if(handle.getVideoControl() != null){
+            double time = handle.getVideoControl().getCurrentPosition();
+            manager.process(time);
+        }
+    }
+    
+    public double getInterval() {
+        return EVENT_INTERVAL;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataContentProvider.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataContentProvider.java
new file mode 100644
index 0000000..d7574c1
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataContentProvider.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.impl;
+
+import org.eclipse.actf.ai.audio.description.IMetadata;
+import org.eclipse.actf.ai.audio.description.IMetadataProvider;
+import org.eclipse.actf.ai.audio.description.util.TimeFormatUtil;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.graphics.Image;
+
+
+
+
+public class MetadataContentProvider extends LabelProvider implements IStructuredContentProvider, ITableLabelProvider{
+
+    @Override
+    public void dispose() {
+    }
+
+    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+    }
+
+    public Object[] getElements(Object inputElement) {
+        if(inputElement instanceof IMetadataProvider){
+            IMetadataProvider provider = (IMetadataProvider)inputElement;
+            
+            String[][] ret = new String[provider.getSize()][2];
+            for(int i=0; i<ret.length; i++){
+                IMetadata metadata = provider.getItem(i);
+                ret[i][0] = ""+TimeFormatUtil.getTimeString(metadata.getStartTime()/100.0);
+                ret[i][1] = ""+metadata.getDescription();
+            }
+            return ret;
+        }
+        return null;
+    }
+
+    public Image getColumnImage(Object element, int columnIndex) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public String getColumnText(Object element, int columnIndex) {
+        if(element instanceof String[]){
+            return ((String[])element)[columnIndex];
+        }
+        return "";
+    }
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataImpl.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataImpl.java
new file mode 100644
index 0000000..abf203e
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataImpl.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.description.impl;
+
+import java.util.Locale;
+
+import org.eclipse.actf.ai.audio.description.IMetadata;
+
+
+public class MetadataImpl implements IMetadata {
+    private double scale = 1;
+
+    private String sTime;
+
+    private int type = MASK_SPEAK;
+
+    private int start;
+
+    private int originalTime;
+
+    private int duration;
+
+    private String description;
+
+    private int importance;
+
+    private String lang;
+
+    public MetadataImpl(String start, String duration, String desc, String lang, String importance) {
+        setStart(start);
+        setDuration(duration);
+        setLang(lang);
+        setDescription(desc);
+        setImportance(importance);
+
+    }
+
+    private void setStart(String start) {
+        this.start = string2time(start);
+        setStringTime();
+    }
+
+    private void setDuration(String duration) {
+        this.duration = string2time(duration);
+    }
+
+    private void setLang(String lang) {
+        this.lang = (new Locale(lang)).getLanguage();
+    }
+
+    private void setDescription(String desc) {
+        this.description = desc;
+    }
+
+    private void setImportance(String importance) {
+        importance = importance.toLowerCase();
+        if (importance.equals("low")) {
+            this.importance = IMPORTANCE_LOW;
+        } else if (importance.equals("middle")) {
+            this.importance = IMPORTANCE_MIDDLE;
+        } else if (importance.equals("high")) {
+            this.importance = IMPORTANCE_HIGH;
+        }
+    }
+
+    private int string2time(String str) {
+        int time = 0;
+        try {
+            String[] part = str.split(":");
+            int[] r = new int[] { 1, 1000, 60000, 3600000 };
+            
+            time = 0;
+            for (int i = part.length - 1, j = 0; i >= 0; i--, j++) {
+                int temp = Integer.parseInt(part[i]);
+                time += temp * r[j];
+            }
+            time /= 10;
+        } catch (Exception e) {
+            e.printStackTrace();
+            time = 0;
+        }
+        return time;
+    }
+
+    private void setStringTime() {
+        int temp = getStartTime();
+        int mm = 0;
+        int s = 0;
+        int m = 0;
+        int h = 0;
+        mm = temp % 100;
+        temp -= mm;
+        temp /= 100;
+        s = temp % 60;
+        temp -= s;
+        temp /= 60;
+        m = temp % 60;
+        temp -= m;
+        temp /= 60;
+        h = temp;
+
+        StringBuffer sb = new StringBuffer();
+        if (h != 0) {
+            sb.append(h + "h");
+        }
+        sb.append((m < 10 ? "0" : "") + m + "m");
+        sb.append((s < 10 ? "0" : "") + s + "s");
+        sb.append((mm < 10 ? "0" : "") + mm);
+
+        sTime = sb.toString();
+    }
+
+    public void setScale(double scale) {
+        this.scale = scale;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public int getStartTime() {
+        int temp = start;
+        if (temp < 0) {
+            temp = 0;
+        }
+        return (int) (temp * scale);
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return start + " (" + duration+") "+description + ", " + importance;
+    }
+
+    public String getStringTime() {
+        return sTime;
+    }
+
+    public void refresh() {
+        start = originalTime;
+        setStringTime();
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public String getLang() {
+        return lang;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataManager.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataManager.java
new file mode 100644
index 0000000..af6d17c
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataManager.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.impl;
+
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.audio.description.IMetadata;
+import org.eclipse.actf.ai.audio.description.IMetadataProvider;
+import org.eclipse.actf.ai.fennec.treemanager.ISoundControl;
+import org.eclipse.actf.ai.fennec.treemanager.IVideoControl;
+import org.eclipse.actf.ai.navigator.IMediaControl.IHandle;
+
+
+
+
+public class MetadataManager{
+
+    private IMetadataProvider metadataProvider;
+
+    private int index;
+
+    private int oldIndex;
+
+    private int forceFlag = IMetadata.MASK_NONE;
+    
+    private IHandle handle;
+
+    private IVideoControl video;
+
+    @SuppressWarnings("unused")
+    private ISoundControl sound;
+    
+    private boolean stopFlag = false;
+
+    public void setForceFlag(int flag) {
+        forceFlag = flag;
+    }
+
+    public MetadataManager(IHandle handle, IMetadataProvider metadataProvider) {
+        this.handle = handle;
+        this.video = handle.getVideoControl();
+        this.sound = handle.getSoundControl();
+        
+        this.metadataProvider = metadataProvider;
+        index = oldIndex = -1;
+    }
+    
+    private boolean pauseBeforeFlag = false;
+
+    private boolean speakFlag = false;
+
+    private boolean pauseAfterFlag = false;
+
+    public void process(double time) {
+        if(stopFlag)
+            return;
+        
+        DescriptionPlugin.getDefault().getDescriptionView().setTime(time);
+
+        if (metadataProvider == null)
+            return;
+        index = metadataProvider.getIndex((int) (time * 100));
+        // System.out.println(time+", "+metadataProvider.getItem(index).getTime());
+
+        if (oldIndex != index && oldIndex < index) {
+            int type = metadataProvider.getItem(index).getType();
+
+            pauseBeforeFlag = isIt(type | forceFlag, IMetadata.MASK_PAUSE_BEFORE);
+            speakFlag = isIt(type | forceFlag, IMetadata.MASK_SPEAK);
+            pauseAfterFlag = isIt(type | forceFlag, IMetadata.MASK_PAUSE_AFTER);
+        }
+
+        if (pauseBeforeFlag) {
+            pauseBeforeFlag = false;
+            processPause();
+        }
+        if (speakFlag) {
+            speakFlag = false;
+            processSpeak();
+        }
+        if (pauseAfterFlag) {
+            pauseAfterFlag = false;
+            processPlay();
+        }
+
+        oldIndex = index;
+    }
+
+    private boolean isIt(int type, int pause) {
+        return (type & pause) == pause;
+    }
+
+    private void processPause() {
+        if (DescriptionPlugin.getDefault().getEnable())
+            video.pauseMedia();
+    }
+
+    private void processPlay() {
+        if (DescriptionPlugin.getDefault().getEnable())
+            video.playMedia();
+    }
+
+    private void processSpeak() {
+        if (DescriptionPlugin.getDefault().getEnable()) {
+            
+            /*
+            int[] volumes = null;
+            volumes = sound.getVolumes();        
+            int[] volumes2 = new int[volumes.length];
+            for(int i=0; i<volumes2.length; i++)
+                volumes2[i] = 200;
+            sound.setVolumes(volumes2);
+            */
+            
+            String desc = metadataProvider.getItem(index).getDescription();
+            say(desc);
+        }
+    }
+
+    public void say(String str) {
+        //System.out.println(str);
+        DescriptionPlugin plugin = DescriptionPlugin.getDefault();
+        
+        if (plugin.canSpeak()) {
+            plugin.speak(str);
+        } else {
+            handle.getVoice().speak(str, true);
+        }
+    }
+    
+    public void stop() {
+        stopFlag = true;
+    }
+    
+    public void start(){
+        stopFlag = false;
+        DescriptionPlugin.getDefault().getDescriptionView().setInput(metadataProvider);
+    }
+    
+    public boolean hasMetadata(){
+        if (metadataProvider == null)
+            return false;
+        return metadataProvider.hasMetadata();
+    }
+
+    public Object getMetadataProvider() {
+        return metadataProvider;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataProviderImpl.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataProviderImpl.java
new file mode 100644
index 0000000..a05afff
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/impl/MetadataProviderImpl.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.description.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+import java.util.Stack;
+
+import org.eclipse.actf.ai.audio.description.IMetadata;
+import org.eclipse.actf.ai.audio.description.IMetadataProvider;
+import org.eclipse.actf.ai.xmlstore.IXMLInfo;
+import org.eclipse.actf.ai.xmlstore.XMLStoreException;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+public class MetadataProviderImpl implements IMetadataProvider {
+
+    private static class Key {
+        public String id;
+
+        public String lang;
+
+        public Key(String id, String lang) {
+            this.id = id;
+            if (lang == null || lang.length() == 0) {
+                this.lang = "";
+            } else {
+                this.lang = (new Locale(lang)).getLanguage();
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return toString().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return toString().equals(o.toString());
+        }
+
+        @Override
+        public String toString(){
+            return id+lang;
+        }
+    }
+
+    private HashMap<Key, ArrayList<IMetadata>> metadataList = new HashMap<Key, ArrayList<IMetadata>>();
+
+    private ArrayList<IMetadata> metadata;
+
+    private ArrayList<IXMLInfo> entries;
+
+    private Locale locale = null;
+
+    public MetadataProviderImpl(ArrayList<IXMLInfo> entries) {
+        this.entries = entries;
+    }
+
+    public void reload() {
+        metadataList.clear();
+        readFile();
+    }
+
+    private Stack<String> langStack = new Stack<String>();
+
+    private class BaseHandler extends DefaultHandler {
+        private BaseHandler back;
+
+        private IXMLInfo entry;
+
+        public BaseHandler(BaseHandler back, IXMLInfo entry) {
+            this.back = back;
+            this.entry = entry;
+        }
+
+        public BaseHandler(BaseHandler back) {
+            this.back = back;
+            this.entry = back.getEntry();
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            if (attributes.getValue("xml:lang") != null) {
+                langStack.push(attributes.getValue("xml:lang"));
+            } else {
+                langStack.push(langStack.peek() + "");
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            langStack.pop();
+            getEntry().setContentHandler(back);
+        }
+
+        public IXMLInfo getEntry() {
+            return entry;
+        }
+    }
+
+    private class AllHandler extends BaseHandler {
+        private boolean altFlag;
+
+        public AllHandler(IXMLInfo entry) {
+            super(null, entry);
+            langStack.clear();
+            langStack.push("");
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            super.startElement(uri, localName, qName, attributes);
+
+            if (altFlag) {
+                if ("item".equals(localName)) {
+                    String importance = attributes.getValue("importance");
+                    if (importance == null || importance.length() == 0) {
+                        importance = "middle";
+                    }
+                    getEntry().setContentHandler(new ItemHandler(this, importance));
+                }
+            } else {
+                if ("alternative".equals(localName)) {
+                    String type = attributes.getValue("type");
+                    if (type != null && type.equals("audio-description")) {
+                        altFlag = true;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            // super.endElement(uri, localName, qName);
+        }
+    }
+
+    private class ItemHandler extends BaseHandler {
+        private boolean descFlag = false;
+
+        private boolean startFlag = false;
+
+        private boolean durationFlag = false;
+
+        private StringBuffer buf = new StringBuffer();
+
+        private String start;
+
+        private String duration;
+
+        private ArrayList<String> desc = new ArrayList<String>();
+
+        private ArrayList<String> lang = new ArrayList<String>();
+
+        private String importance;
+
+        public ItemHandler(BaseHandler back, String importance) {
+            super(back);
+            this.importance = importance;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            super.startElement(uri, localName, qName, attributes);
+
+            if ("start".equals(localName)) {
+                if (attributes.getValue("type").equals("relTime")) {
+                    startFlag = true;
+                }
+            } else if ("duration".equals(localName)) {
+                durationFlag = true;
+            } else if ("description".equals(localName)) {
+                descFlag = true;
+            }
+
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (startFlag) {
+                buf.append(ch, start, length);
+            } else if (durationFlag) {
+                buf.append(ch, start, length);
+            } else if (descFlag) {
+                buf.append(ch, start, length);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if ("start".equals(localName)) {
+                startFlag = false;
+                start = buf.toString();
+                buf.delete(0, buf.length());
+            } else if ("duration".equals(localName)) {
+                durationFlag = false;
+                duration = buf.toString();
+                buf.delete(0, buf.length());
+            } else if ("description".equals(localName)) {
+                descFlag = false;
+                desc.add(buf.toString());
+                lang.add(langStack.peek());
+                buf.delete(0, buf.length());
+            } else if ("item".equals(localName)) {
+
+                for (int i = 0; i < desc.size(); i++) {
+                    MetadataImpl mi = new MetadataImpl(start, duration, desc.get(i), lang.get(i), importance);
+                    //System.out.println(start + ", " + duration + ", " + desc.get(i) + ", " + lang.get(i));
+
+                    Key key = new Key(getEntry().getDocumentation(), lang.get(i));
+                    
+                    ArrayList<IMetadata> list = metadataList.get(key);
+                    
+                    if (list == null) {
+                        list = new ArrayList<IMetadata>();
+                        metadataList.put(key, list);
+                    }
+                    list.add(mi);
+                }
+                super.endElement(uri, localName, qName);
+            }
+        }
+    }
+
+    private void readFile() {
+        for (int i = 0; i < entries.size(); i++) {
+            IXMLInfo entry = entries.get(i);
+
+            AllHandler ah = new AllHandler(entry);
+            entry.setContentHandler(ah);
+            try {
+                entry.startSAX();
+            } catch (XMLStoreException e) {
+                e.printStackTrace();
+            } catch (SAXException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+    ArrayList<ArrayList<IMetadata>> alternatives;
+
+    public int getAlternatives() {
+        Set<Key> keys = metadataList.keySet();
+        alternatives = new ArrayList<ArrayList<IMetadata>>();
+        for (Iterator<Key> i = keys.iterator(); i.hasNext();) {
+            Key key = i.next();
+            if (key.lang.equals(locale.getLanguage())) {
+                alternatives.add(metadataList.get(key));
+            }
+        }
+        if (alternatives.size() > 0)
+            setMetadata(alternatives.get(0));
+        return alternatives.size();
+    }
+
+    public void setAlternative(int index) {
+        metadata = alternatives.get(index);
+    }
+
+    public void setMetadata(ArrayList<IMetadata> metadata) {
+        this.metadata = metadata;
+    }
+
+    public IMetadata getItem(int index) {
+        if (0 <= index && index < metadata.size())
+            return metadata.get(index);
+        return null;
+    }
+
+    public ArrayList<IMetadata> getAllItems() {
+        return metadata;
+    }
+
+    public int getIndex(int position) {
+        if (metadata == null)
+            return 0;
+
+        int ret = 0;
+        for (int i = 0; i < metadata.size(); i++) {
+            if (metadata.get(i).getStartTime() >= position) {
+                ret = i - 1;
+                return ret;
+            }
+        }
+        return metadata.size() - 1;
+    }
+
+    public int getSize() {
+        if (metadata == null)
+            return 0;
+        return metadata.size();
+    }
+    
+    public boolean hasMetadata() {
+        return metadata.size() > 0;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferenceInitializer.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferenceInitializer.java
new file mode 100644
index 0000000..6ce4251
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferenceInitializer.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.description.preferences;
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.voice.internal.TTSRegistry;
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+
+public class ADPreferenceInitializer extends AbstractPreferenceInitializer {
+
+	@Override
+    public void initializeDefaultPreferences() {
+		IPreferenceStore store = DescriptionPlugin.getDefault().getPreferenceStore();
+		store.setDefault(DescriptionPlugin.PREF_ENGINE, TTSRegistry.getDefaultEngine());
+	}
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferencePage.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferencePage.java
new file mode 100644
index 0000000..6bca77d
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/preferences/ADPreferencePage.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.preferences;
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.audio.description.Messages;
+import org.eclipse.actf.ai.voice.internal.TTSRegistry;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.RadioGroupFieldEditor;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+
+
+
+public class ADPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage{
+
+    public ADPreferencePage() {
+        super(GRID);
+        setPreferenceStore(DescriptionPlugin.getDefault().getPreferenceStore());
+        setDescription(Messages.getString("AudioDescription.preference.title")); //$NON-NLS-1$
+    }
+
+    @Override
+    public void createFieldEditors() {
+        RadioGroupFieldEditor rgfe;
+        String[][] labelAndIds = TTSRegistry.getLabelAndIds();
+        addField(rgfe = new RadioGroupFieldEditor(DescriptionPlugin.PREF_ENGINE,
+                Messages.getString("AudioDescription.voice.engine"), 1, labelAndIds, //$NON-NLS-1$
+                getFieldEditorParent()));   
+        Composite c = rgfe.getRadioBoxControl(getFieldEditorParent());
+        for(int i=0; i<labelAndIds.length; i++){
+            if(labelAndIds[i][1].length() == 0){
+                c.getChildren()[i].setEnabled(false);
+            }
+        }
+    }
+
+    public void init(IWorkbench workbench) {
+
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/util/TimeFormatUtil.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/util/TimeFormatUtil.java
new file mode 100644
index 0000000..4749921
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/util/TimeFormatUtil.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.util;
+
+
+public class TimeFormatUtil {
+
+    public static String getTimeString(double time){
+        int mi, se, ms;
+        int intTime = (int)(time*100);
+        
+        ms = intTime%100;
+        intTime /= 100;
+        se = intTime%60;
+        intTime /= 60;
+        mi = intTime;
+        
+        return (mi<10?"0":"")+mi+":"+(se<10?"0":"")+se+"."+(ms<10?"0":"")+ms;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/DescriptionView.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/DescriptionView.java
new file mode 100644
index 0000000..136cbf8
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/DescriptionView.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.views;
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.audio.description.Messages;
+import org.eclipse.actf.ai.audio.description.impl.MetadataContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.ViewPart;
+
+
+
+
+public class DescriptionView extends ViewPart {
+    public static final String ID = "org.eclipse.actf.ai.audio.description.views.DescriptionView";
+
+    private VideoStatusViewer status;
+
+    private TableViewer tableViewer;
+    
+    @Override
+    public void createPartControl(Composite parent) {
+        initView(parent);
+        DescriptionPlugin.getDefault().setDescriptionView(this);
+    }
+
+    private void initView(Composite parent) {
+        GridLayout gLayout = new GridLayout(1, true);
+        gLayout.marginWidth = 0;
+        gLayout.marginHeight = 0;
+        parent.setLayout(gLayout);
+
+        status = new VideoStatusViewer(parent);
+
+        tableViewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+        MetadataContentProvider provider = new MetadataContentProvider();
+        tableViewer.setContentProvider(provider);
+        tableViewer.setLabelProvider(provider);
+
+        Table table = tableViewer.getTable();
+        table.setHeaderVisible(true);
+        table.setLinesVisible(true);
+        TableColumn tableColumnProperty = new TableColumn(table, SWT.LEFT);
+        tableColumnProperty.setText(Messages.getString("AudioDescription.view.time"));
+        tableColumnProperty.setWidth(70);
+        TableColumn tableColumnValue = new TableColumn(table, SWT.LEFT);
+        tableColumnValue.setText(Messages.getString("AudioDescription.view.desc"));
+        tableColumnValue.setWidth(130);
+        
+        GridData data = new GridData(GridData.FILL_BOTH);
+        table.setLayoutData(data);
+    }
+
+    @Override
+    public void setFocus() {
+
+    }
+
+    public void setTime(double time) {
+        status.setTime(time);
+    }
+    public void setInput(Object input){
+        if(tableViewer.getTable().isDisposed())
+            return;
+        tableViewer.setInput(input);
+    }
+    public void setIndex(int index){
+        if(tableViewer.getTable().isDisposed())
+            return;
+        tableViewer.getTable().select(index);
+    }
+
+    public void setEnable(boolean b) {
+        status.setEnable(b);
+    }
+    
+    public boolean toggleViewShowing() {
+        IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+        IViewPart part = page.findView(ID);
+
+        if (part != null) {
+            page.hideView(part);
+            return false;
+        } else {
+            try {
+                page.showView(ID);
+                return true;
+            } catch (PartInitException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/VideoStatusViewer.java b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/VideoStatusViewer.java
new file mode 100644
index 0000000..fa0328a
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.description/src/org/eclipse/actf/ai/audio/description/views/VideoStatusViewer.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.description.views;
+
+import org.eclipse.actf.ai.audio.description.DescriptionPlugin;
+import org.eclipse.actf.ai.audio.description.Messages;
+import org.eclipse.actf.ai.audio.description.util.TimeFormatUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+
+
+
+public class VideoStatusViewer {
+    
+    private Composite parent;
+    private Label status;
+    private boolean enable = false;
+    private String timeText;
+
+
+    public VideoStatusViewer(Composite parent){
+        this.parent = parent;
+        initialize();
+    }
+    
+    public void initialize(){
+        status = new Label(parent, SWT.NONE);
+        GridData data = new GridData(GridData.FILL_HORIZONTAL);
+        status.setLayoutData(data);
+        status.setAlignment(SWT.RIGHT);
+        setTime(0);
+        enable = DescriptionPlugin.getDefault().getEnable();
+    }
+    
+    public void setTime(double time){
+        timeText = TimeFormatUtil.getTimeString(time);
+        refreshText();
+    }
+    
+    private void refreshText(){
+        if(status.isDisposed())
+            return;
+        if(enable){
+            status.setText(Messages.getString("AudioDescription.view.enable")+" "+timeText);
+        }else{
+            status.setText(Messages.getString("AudioDescription.view.notEnable")+" "+timeText);
+        }
+    }
+
+    public void setEnable(boolean b) {
+        enable = b;
+        refreshText();
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/.classpath b/plugins/org.eclipse.actf.ai.audio.io/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.actf.ai.audio.io/.project b/plugins/org.eclipse.actf.ai.audio.io/.project
new file mode 100644
index 0000000..cc0b860
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.actf.ai.audio.io</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.actf.ai.audio.io/META-INF/MANIFEST.MF b/plugins/org.eclipse.actf.ai.audio.io/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..89dee95
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/META-INF/MANIFEST.MF
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Eclipse ACTF Audio IO Plug-in (Incubation)
+Bundle-SymbolicName: org.eclipse.actf.ai.audio.io
+Bundle-Version: 0.0.1
+Bundle-Activator: org.eclipse.actf.ai.audio.io.Activator
+Bundle-Vendor: Eclipse.org
+Bundle-Localization: plugin
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime
+Eclipse-LazyStart: true
+Export-Package: org.eclipse.actf.ai.audio.io
diff --git a/plugins/org.eclipse.actf.ai.audio.io/about.html b/plugins/org.eclipse.actf.ai.audio.io/about.html
new file mode 100644
index 0000000..481dbcf
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/about.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 5, 2006</p>	
+<h3>License</h3>
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  
+Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/org/documents/epl-v10.php">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor&rsquo;s license 
+that was provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.actf.ai.audio.io/build.properties b/plugins/org.eclipse.actf.ai.audio.io/build.properties
new file mode 100644
index 0000000..e1942be
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/build.properties
@@ -0,0 +1,15 @@
+###############################################################################
+# Copyright (c) 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+			   about.html,\
+               .
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/Activator.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/Activator.java
new file mode 100644
index 0000000..f948ffd
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/Activator.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.actf.ai.audio.io;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.eclipse.actf.ai.audio.io";
+
+	// The shared instance
+	private static Activator plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public Activator() {
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	@Override
+    public void start(BundleContext context) throws Exception {
+		super.start(context);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	@Override
+    public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static Activator getDefault() {
+		return plugin;
+	}
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioFactory.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioFactory.java
new file mode 100644
index 0000000..ea98776
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioFactory.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+import java.net.URL;
+
+import javax.sound.sampled.Mixer;
+
+import org.eclipse.actf.ai.audio.io.impl.AudioFileReader;
+import org.eclipse.actf.ai.audio.io.impl.AudioPipe;
+import org.eclipse.actf.ai.audio.io.impl.LoopedAudioPipe;
+import org.eclipse.actf.ai.audio.io.impl.PlaybackDevice;
+import org.eclipse.actf.ai.audio.io.impl.RecordDevice;
+
+
+
+
+public class AudioFactory {
+    public static IAudioReader createAudioReader(String file) {
+        return new AudioFileReader(file);
+    }
+
+    public static IAudioReader createAudioReader(URL url) {
+        return new AudioFileReader(url);
+    }
+    
+    public static IAudioWriter createDefaultWriter() {
+        return new PlaybackDevice();
+    }
+
+    public static IAudioPipe createAudioPipe(IAudioReader reader, IAudioWriter writer) {
+        return new AudioPipe(reader, writer);
+    }
+    
+    public static IAudioPipe createLoopedAudioPipe(IAudioReader reader, IAudioWriter writer) {
+        return new LoopedAudioPipe(reader, writer);
+    }
+
+    public static IAudioWriter createAudioWriter(Mixer mixer) {
+        return new PlaybackDevice(mixer);
+    }
+
+    public static IAudioReader createAudioReader(Mixer mixer, int recordingSmapleRate, int recordingChannels) {
+        return new RecordDevice(mixer, recordingSmapleRate, recordingChannels);
+    }
+
+    public static void main(String[] args) {
+        IAudioReader reader = AudioFactory.createAudioReader("16-16-short-pcm-lpf.wav");
+        IAudioWriter writer = AudioFactory.createDefaultWriter();
+        IAudioPipe pipe = AudioFactory.createLoopedAudioPipe(reader, writer);
+        
+        // buffer size for reading (in milli seconds)
+        pipe.setBufferSize(100);    
+        
+        // interval time to try writing (in milli seconds)
+        pipe.setInterval(1);       
+
+        // Create new thread and start playing. 
+        // The pipe automatically opens reader and writer.
+        pipe.prepare();
+        
+        pipe.start(); 
+        
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+            
+        pipe.stop();
+        
+        pipe.start(); 
+        
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+            
+        pipe.stop();
+    }
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioIOException.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioIOException.java
new file mode 100644
index 0000000..8fea0c6
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioIOException.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+
+public class AudioIOException extends Exception{
+
+    public AudioIOException(String string) {
+        super(string);
+    }
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioPipeListener.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioPipeListener.java
new file mode 100644
index 0000000..1395187
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioPipeListener.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+
+public interface AudioPipeListener {
+    void finished(IAudioPipe pipe);
+    void stopped(IAudioPipe pipe);
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioUtil.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioUtil.java
new file mode 100644
index 0000000..f61869b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/AudioUtil.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+import java.nio.ByteOrder;
+
+import javax.sound.sampled.AudioFormat;
+
+
+
+public class AudioUtil {
+
+    public static int getBytesPerSecondLR(AudioFormat format) {
+        return (int) getBytesPerSecond(format) * format.getChannels();
+    }
+
+    public static int getBytesPerSecond(AudioFormat format) {
+        return (int) getBitsPerSecond(format) / 8;
+    }
+    
+    public static int getBitsPerSecond(AudioFormat format){
+        return (int) format.getSampleRate() * format.getSampleSizeInBits();
+    }
+
+    public static int getSamplesPerSecond(AudioFormat format) {
+        return (int) format.getSampleRate();
+    }
+
+    public static int getSamplesPerSecondLR(AudioFormat format) {
+        return (int) getSamplesPerSecond(format) * format.getChannels();
+    }
+
+    public static int getBytesPerSample(AudioFormat format){
+        return format.getSampleSizeInBits() / 8;
+    }
+
+    public static int getBytesPerSampleLR(AudioFormat format) {
+        return getBytesPerSample(format) * format.getChannels();
+    }
+
+    public static ByteOrder getEndian(AudioFormat format) {
+        if(format.isBigEndian())
+            return ByteOrder.BIG_ENDIAN;
+        else
+            return ByteOrder.LITTLE_ENDIAN;
+    }
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioPipe.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioPipe.java
new file mode 100644
index 0000000..11899e2
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioPipe.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+
+public interface IAudioPipe {
+    void addAudioPipeListener(AudioPipeListener listener);
+    
+    void setInterval(int interval);
+    void setPriority(int priority);
+    void setBufferSize(int miliSeconds);
+    boolean isActive();
+    
+    void prepare();
+    void start(int delay);
+    void start();
+    void stop();
+    void join();
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioReader.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioReader.java
new file mode 100644
index 0000000..c1d4a9b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioReader.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+import javax.sound.sampled.AudioFormat;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+
+
+
+
+
+public interface IAudioReader {
+    
+    String getName();
+    
+    AudioFormat getAudioFormat();
+    
+    boolean canRead();
+    
+    void open() throws AudioIOException;
+    
+    void close();
+    
+    int read(byte[] data, int offset, int length) throws AudioIOException;
+
+    boolean isClosed();
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioWriter.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioWriter.java
new file mode 100644
index 0000000..381299c
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/IAudioWriter.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io;
+
+import javax.sound.sampled.AudioFormat;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+
+
+
+
+
+public interface IAudioWriter {
+    
+    String getName();
+    
+    int write(byte[] data, int offset, int length) throws AudioIOException;
+
+    boolean canWrite();
+    
+    void open(AudioFormat format) throws AudioIOException;
+
+    void close();
+
+    boolean isClosed();
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioFileReader.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioFileReader.java
new file mode 100644
index 0000000..2db216b
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioFileReader.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+import org.eclipse.actf.ai.audio.io.IAudioReader;
+
+
+
+
+public class AudioFileReader implements IAudioReader {
+
+    private File file;
+
+    private URL url;
+
+    private AudioFormat format;
+
+    private AudioInputStream stream;
+
+    private boolean opened = false;
+
+    private boolean closed = false;
+
+    public AudioFileReader(String fileName) {
+        setFile(fileName);
+    }
+
+    public AudioFileReader(URL url) {
+        setURL(url);
+    }
+
+    public String getName() {
+        if (file != null)
+            return "File \"" + file.getName() + "\"";
+        if (url != null)
+            return "URL \"" + url.getPath() + "\"";
+        return "";
+    }
+
+    private void setFile(String fileName) {
+        this.file = new File(fileName);
+    }
+
+    private void setURL(URL url) {
+        this.url = url;
+    }
+
+    public boolean canRead() {
+        try {
+            if (file != null)
+                AudioSystem.getAudioInputStream(file);
+            if (url != null)
+                AudioSystem.getAudioFileFormat(url);
+            return true;
+        } catch (UnsupportedAudioFileException e) {
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    synchronized public void open() throws AudioIOException {
+        try {
+            if (file != null)
+                stream = AudioSystem.getAudioInputStream(file);
+            if (url != null)
+                stream = AudioSystem.getAudioInputStream(url);
+            AudioFormat baseFormat = stream.getFormat();
+
+            AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(),
+                    16, baseFormat.getChannels(), baseFormat.getChannels() * 2, baseFormat.getSampleRate(), false);
+
+            stream = AudioSystem.getAudioInputStream(decodedFormat, stream);
+
+            format = stream.getFormat();
+            opened = true;
+            closed = false;
+        } catch (UnsupportedAudioFileException e) {
+            throw new AudioIOException("Unsupported audio file type");
+        } catch (IOException e) {
+            throw new AudioIOException(e.toString());
+        }
+    }
+
+    public AudioFormat getAudioFormat() {
+        return format;
+    }
+
+    synchronized public int read(byte[] data, int offset, int length) throws AudioIOException {
+        if (!opened) {
+            throw new AudioIOException("not opened");
+        }
+        try {
+            int nBytesRead = stream.read(data, offset, length);
+            if (nBytesRead == -1) {
+                stream.close();
+                opened = false;
+                closed = true;
+                nBytesRead = 0;
+            }
+            return nBytesRead;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return 0;
+    }
+
+    synchronized public void close() {
+        try {
+            if (stream != null)
+                stream.close();
+        } catch (IOException e) {
+        }
+        opened = false;
+        closed = true;
+    }
+
+    public boolean isClosed() {
+        return closed;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioPipe.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioPipe.java
new file mode 100644
index 0000000..7059bab
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/AudioPipe.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io.impl;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+
+import javax.sound.sampled.AudioFormat;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+import org.eclipse.actf.ai.audio.io.AudioPipeListener;
+import org.eclipse.actf.ai.audio.io.AudioUtil;
+import org.eclipse.actf.ai.audio.io.IAudioPipe;
+import org.eclipse.actf.ai.audio.io.IAudioReader;
+import org.eclipse.actf.ai.audio.io.IAudioWriter;
+
+
+
+
+public class AudioPipe implements IAudioPipe{
+
+    private IAudioReader reader;
+
+    private IAudioWriter writer;
+
+    private AudioFormat format;
+
+    private Thread thread;
+
+    private Runnable runner;
+
+    private byte[] buffer;
+
+    private int bufferSize;
+
+    private int interval = 100;
+    
+    private int priority = Thread.NORM_PRIORITY;
+
+    private int bufferSizeInMs = 1000;
+    
+    private boolean active;
+    
+    private boolean waiting;
+    
+    private boolean stopFlag = false;
+    
+    private boolean stopFlag2 = false;
+    
+    private ArrayList<AudioPipeListener> listeners = new ArrayList<AudioPipeListener>();
+
+    public AudioPipe(IAudioReader reader, IAudioWriter writer) {
+        this.reader = reader;
+        this.writer = writer;
+        runner = new ReaderWriterRunner();
+    }
+    
+    public void addAudioPipeListener(AudioPipeListener listener) {
+        listeners.add(listener);
+    }
+
+    class ReaderWriterRunner implements Runnable {
+        private void fadeout(byte[] buffer, int nBytesRead) {
+            int bps = AudioUtil.getBytesPerSample(format);
+            
+            ByteBuffer bb = ByteBuffer.wrap(buffer);
+            ShortBuffer sb = null;
+            if (format.isBigEndian())
+                bb.order(ByteOrder.BIG_ENDIAN);
+            else
+                bb.order(ByteOrder.LITTLE_ENDIAN);
+            
+            if (bps == 2)
+                sb = bb.asShortBuffer();
+            
+            int len = nBytesRead / bps;
+            
+            for (int i = 0; i < len; i++) {
+                double k = (double) (len - i) / (double) len;
+                if (bps == 1)
+                    bb.put(i, (byte) (bb.get(i) * k));
+                else
+                    sb.put(i, (short) (sb.get(i) * k));
+            }
+        }
+        
+        public void run() {
+            try {
+                while (true) {
+                    if (reader.isClosed())
+                        break;
+                    
+                    int nBytesRead = reader.read(buffer, 0, buffer.length);
+                    
+                    int nBytesWritten = 0;
+                    do {
+                        if (writer.isClosed()){
+                            active = false;
+                            fireStopped();
+                            return;
+                        }
+                        if (stopFlag) {
+                            fadeout(buffer, nBytesRead);
+                            stopFlag = false;
+                            stopFlag2 = true;
+                        }
+                        int temp = writer.write(buffer, nBytesWritten, nBytesRead);
+                        nBytesWritten += temp;
+                        nBytesRead -= temp;
+                        sleep();
+                    } while (nBytesRead > 0);
+                    
+                    if (stopFlag2) {
+                        stopFlag2 = false;
+                        writer.close();
+                    }
+                }
+                writer.close();
+            } catch (AudioIOException e) {
+                writer.close();
+            } catch (Exception e){
+                e.printStackTrace();
+                writer.close();
+            }
+            active = false;
+            fireFinished();
+        }
+    }
+    
+    private void fireFinished() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).finished(this);
+        }
+    }
+    
+    private void fireStopped() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).stopped(this);
+        }
+    }
+    
+    synchronized public void setInterval(int interval) {
+        this.interval = interval;
+    }
+    
+    synchronized public void setPriority(int priority){
+        this.priority = priority;
+        if(thread != null)
+            thread.setPriority(priority);
+    }
+
+    public void prepare() {
+        try {
+            reader.open();
+            format = reader.getAudioFormat();
+            writer.open(format);
+        } catch (AudioIOException e) {
+            e.printStackTrace();
+            return;
+        }
+    }
+    
+    public void start() {      
+        start(0);
+    }
+
+    public void start(final int delay) {
+        if (active || waiting)
+            return;
+        waiting = true;
+        thread = new Thread(new Runnable() {
+            public void run(){
+                long s = (new Date()).getTime();
+                
+                while (true) {
+                    long n = (new Date()).getTime();
+                    if (stopFlag) {
+                        waiting = false;
+                        stopFlag = false;
+                        active = false;
+                        fireStopped();
+                        return;
+                    }
+                    if (s + delay <= n) {
+                        break;
+                    }
+                    try {
+                        Thread.sleep(1);
+                    } catch (InterruptedException e){
+                    }
+                }
+                
+                while (active) {
+                    try {
+                        Thread.sleep(1);
+                    } catch (InterruptedException e){
+                    }
+                }
+
+                prepare();
+
+                int Bps = AudioUtil.getBytesPerSampleLR(format);
+                bufferSize = AudioUtil.getBytesPerSecondLR(format) * bufferSizeInMs / 1000 / Bps * Bps;
+                buffer = new byte[bufferSize];
+                active = true;
+                waiting = false;
+                runner.run();
+            }
+        });
+        thread.setPriority(priority);
+        thread.setName(reader.getName()+"<->"+writer.getName());
+        thread.start();
+    }
+
+    synchronized private void sleep() {
+        try {
+            Thread.sleep(interval);
+            Thread.yield();
+        } catch (InterruptedException e) {
+        }
+    }
+    
+    public boolean isActive(){
+        return active;
+    }
+    
+    public void stop() {
+        if (active || waiting)
+            stopFlag = true;
+    }
+    
+    public void join() {
+        if(thread == null)
+            return;
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    public void setBufferSize(int miliSeconds){
+        bufferSizeInMs  = miliSeconds;
+    }
+
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/LoopedAudioPipe.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/LoopedAudioPipe.java
new file mode 100644
index 0000000..ff3c059
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/LoopedAudioPipe.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io.impl;
+
+import java.util.ArrayList;
+
+import org.eclipse.actf.ai.audio.io.AudioPipeListener;
+import org.eclipse.actf.ai.audio.io.IAudioPipe;
+import org.eclipse.actf.ai.audio.io.IAudioReader;
+import org.eclipse.actf.ai.audio.io.IAudioWriter;
+
+
+
+
+public class LoopedAudioPipe implements IAudioPipe, AudioPipeListener {
+    
+    IAudioPipe target;
+    
+    boolean isActive;
+    
+    boolean joining = false;
+
+    private ArrayList<AudioPipeListener> listeners = new ArrayList<AudioPipeListener>();
+    
+    public LoopedAudioPipe(IAudioReader reader, IAudioWriter writer) {
+        target = new AudioPipe(reader, writer);
+        target.addAudioPipeListener(this);
+    }
+    
+    public void addAudioPipeListener(AudioPipeListener listener) {
+        listeners.add(listener);
+    }
+
+    public boolean isActive() {
+        return isActive;
+    }
+
+    public void join() {
+        joining = true;
+        target.join();
+        fireFinished();
+    }
+
+    public void setBufferSize(int miliSeconds) {
+        target.setBufferSize(miliSeconds);
+    }
+
+    public void setInterval(int interval) {
+        target.setInterval(interval);
+    }
+
+    public void setPriority(int priority) {
+        target.setPriority(priority);
+    }
+    
+    public void prepare() {
+        target.prepare();
+    }
+
+    public void start() {
+        start(0);
+    }
+
+    public void start(int delay) {
+        target.start(delay);
+        isActive = true;
+        joining = false;
+    }
+
+    public void stop() {
+        target.stop();
+        isActive = false;
+        fireStopped();
+    }
+
+    private void fireFinished() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).finished(this);
+        }
+    }
+
+    private void fireStopped() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).stopped(this);
+        }
+    }
+    
+    public void finished(IAudioPipe pipe) {
+        if (!joining)
+            pipe.start();
+    }
+    
+    public void stopped(IAudioPipe pipe) {
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/PlaybackDevice.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/PlaybackDevice.java
new file mode 100644
index 0000000..71c15f1
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/PlaybackDevice.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io.impl;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.SourceDataLine;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+import org.eclipse.actf.ai.audio.io.IAudioWriter;
+
+
+
+
+public class PlaybackDevice implements IAudioWriter {
+
+    private Mixer playbackMixer;
+
+    private SourceDataLine playbackLine;
+
+    private int limit;
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof PlaybackDevice) {
+            return playbackMixer.equals(((PlaybackDevice) o).playbackMixer);
+        }
+        return false;
+    }
+
+    public PlaybackDevice(Mixer mixer) {
+        this.playbackMixer = mixer;
+    }
+
+    public PlaybackDevice() {
+        this.playbackMixer = null;
+    }
+
+    public String getName() {
+        if (playbackMixer != null)
+            return "Device \"" + playbackMixer.getMixerInfo().getName() + "\"";
+        else
+            return "Device \"default\"";
+    }
+
+    synchronized public void close() {
+        if (playbackLine != null) {
+            playbackLine.drain();
+            playbackLine.close();
+        }
+    }
+
+    public boolean isClosed() {
+        if (playbackLine == null)
+            return true;
+        return !playbackLine.isOpen();
+    }
+
+    public boolean canWrite() {
+        return true;
+    }
+
+    synchronized public void open(AudioFormat format) throws AudioIOException {
+
+        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
+
+        try {
+            if (playbackMixer != null)
+                playbackLine = (SourceDataLine) playbackMixer.getLine(info);
+            else
+                playbackLine = (SourceDataLine) AudioSystem.getLine(info);
+            
+            playbackLine.open();
+            
+            limit = playbackLine.available() / 10 * 5;
+            
+            playbackLine.start();
+        } catch (LineUnavailableException e1) {
+            e1.printStackTrace();
+        }
+    }
+
+    synchronized public int write(byte[] data, int offset, int length) throws AudioIOException {
+        if (playbackLine.available() > limit) {
+            return playbackLine.write(data, offset, length);
+        }
+        return 0;
+    }
+}
diff --git a/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/RecordDevice.java b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/RecordDevice.java
new file mode 100644
index 0000000..8a8cbae
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.audio.io/src/org/eclipse/actf/ai/audio/io/impl/RecordDevice.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and Others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Daisuke SATO - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.audio.io.impl;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.TargetDataLine;
+
+import org.eclipse.actf.ai.audio.io.AudioIOException;
+import org.eclipse.actf.ai.audio.io.IAudioReader;
+
+
+
+
+public class RecordDevice implements IAudioReader {
+
+    private Mixer recordMixer;
+
+    private AudioFormat format;
+
+    private TargetDataLine recordLine;
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof RecordDevice) {
+            RecordDevice rd = (RecordDevice) o;
+            if (recordMixer.equals(rd.recordMixer)) {
+                return format.getChannels() == rd.format.getChannels()
+                        && format.getFrameRate() == rd.format.getFrameRate()
+                        && format.getSampleSizeInBits() == rd.format.getSampleSizeInBits();
+            }
+        }
+        return false;
+    }
+
+    public RecordDevice(Mixer mixer, int sampleRate, int channels) {
+        this.recordMixer = mixer;
+        format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, 16, channels, 2 * channels, sampleRate,
+                false);
+        if (!recordMixer.isLineSupported(new DataLine.Info(TargetDataLine.class, format))) {
+            check(sampleRate, channels);
+        }
+    }
+
+    private void check(int sampleRate, int channels) {
+        AudioFormat temp;
+        int[] samples = new int[] { 48000, 44100, 22050, 16000, 8000 };
+
+        outer: for (int i = 0; i < samples.length; i++) {
+            if (samples[i] < sampleRate) {
+                temp = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, samples[i], 16, channels, 2 * channels,
+                        samples[i], false);
+                if (recordMixer.isLineSupported(new DataLine.Info(TargetDataLine.class, temp))) {
+                    format = temp;
+                    break outer;
+                }
+            }
+        }
+    }
+
+    public String getName() {
+        return "Device \"" + recordMixer.getMixerInfo().getName() + //
+                " (" + (format.getFrameRate() / 1000) + " kHz, " + format.getChannels() + "ch)" + "\"";
+    }
+
+    public AudioFormat getAudioFormat() {
+        return format;
+    }
+
+    synchronized public void close() {
+        if (recordLine != null)
+            recordLine.close();
+    }
+
+    public boolean isClosed() {
+        return !recordLine.isOpen();
+    }
+
+    public boolean canRead(){
+        DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
+        return recordMixer.isLineSupported(info);
+    }
+    
+    synchronized public void open() throws AudioIOException {
+        DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
+
+        try {
+            recordLine = (TargetDataLine) recordMixer.getLine(info);
+            recordLine.open(format);
+            recordLine.start();
+        } catch (LineUnavailableException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    
+
+    synchronized public int read(byte[] data, int offset, int length) throws AudioIOException {
+        return recordLine.read(data, offset, length);
+    }
+
+}