You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by ad...@apache.org on 2015/03/12 22:18:29 UTC

[22/34] wicket git commit: WICKET-5819 Media tags - audio / video implementation

WICKET-5819 Media tags - audio / video implementation

Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/29bb9a4d
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/29bb9a4d
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/29bb9a4d

Branch: refs/heads/master
Commit: 29bb9a4d8f47f5155dcfa6a00c79b12bc3dcc284
Parents: aa859de
Author: klopfdreh <kl...@tobiass-mbp>
Authored: Thu Jan 22 22:52:18 2015 +0100
Committer: Andrea Del Bene <ad...@apache.org>
Committed: Thu Mar 12 22:13:05 2015 +0100

----------------------------------------------------------------------
 .../util/resource/PackageResourceStream.java    |  27 +-
 .../markup/html/media/MediaComponent.java       | 472 +++++++++++++++++++
 .../media/MediaStreamingResourceReference.java  | 306 ++++++++++++
 .../wicket/markup/html/media/MediaUtils.java    |  49 ++
 .../apache/wicket/markup/html/media/Source.java | 257 ++++++++++
 .../apache/wicket/markup/html/media/Track.java  | 274 +++++++++++
 .../wicket/markup/html/media/audio/Audio.java   |  97 ++++
 .../wicket/markup/html/media/video/Video.java   | 208 ++++++++
 .../wicket/markup/html/media/Application.java   |  37 ++
 .../html/media/MediaTagsExtendedTestPage.html   |  12 +
 .../html/media/MediaTagsExtendedTestPage.java   |  55 +++
 .../wicket/markup/html/media/MediaTagsTest.java |  95 ++++
 .../markup/html/media/MediaTagsTestPage.html    |  10 +
 .../markup/html/media/MediaTagsTestPage.java    |  57 +++
 .../wicket/markup/html/media/dummyAudio.mp3     |   0
 .../wicket/markup/html/media/dummyPoster.jpg    |   0
 .../wicket/markup/html/media/dummySubtitles.vtt |   0
 .../wicket/markup/html/media/dummyVideo.m4a     |   0
 18 files changed, 1954 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
index e76926d..4a1a4e6 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
@@ -18,6 +18,7 @@ package org.apache.wicket.core.util.resource;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Locale;
 
 import org.apache.wicket.Application;
 import org.apache.wicket.WicketRuntimeException;
@@ -36,10 +37,10 @@ import org.apache.wicket.util.time.Time;
  * {@link IResourceStreamLocator}.
  *
  * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
+ * @author Tobias Soloschenko
  */
 public class PackageResourceStream extends AbstractResourceStream
 {
-	/** */
 	private static final long serialVersionUID = 1L;
 
 	private final IResourceStream resourceStream;
@@ -56,11 +57,33 @@ public class PackageResourceStream extends AbstractResourceStream
 	 */
 	public PackageResourceStream(Class<?> scope, String path)
 	{
+		this(scope, path, null, null, null);
+	}
+
+	/**
+	 * Obtains an {@link IResourceStream} from the application's
+	 * {@link IResourceStreamLocator#locate(Class, String)}
+	 *
+	 * @param scope
+	 *            This argument will be used to get the class loader for loading the package
+	 *            resource, and to determine what package it is in.
+	 * @param path
+	 *            The path to the resource
+	 * @param locale
+	 *            the locale of the resource to get
+	 * @param style
+	 *            the style of the resource to get
+	 * @param variation
+	 *            the variation of the resource to get
+	 */
+	public PackageResourceStream(Class<?> scope, String path, Locale locale, String style,
+		String variation)
+	{
 		String absolutePath = Packages.absolutePath(scope, path);
 		resourceStream = Application.get()
 			.getResourceSettings()
 			.getResourceStreamLocator()
-			.locate(scope, absolutePath, null, null, null, null, false);
+			.locate(scope, absolutePath, style, variation, locale, null, false);
 
 		if (resourceStream == null)
 		{

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaComponent.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaComponent.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaComponent.java
new file mode 100755
index 0000000..02c2b8d
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaComponent.java
@@ -0,0 +1,472 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * The media component is used to provide basic functionality to the video and audo component. The
+ * given media streaming resource reference supports Content-Ranges and other stuff to make the
+ * audio and video playback smooth.
+ * 
+ * @author Tobias Soloschenko
+ * @author Andrew Lombardi
+ */
+public abstract class MediaComponent extends WebMarkupContainer
+{
+
+	private static final long serialVersionUID = 1L;
+
+	// use Boolean instead of elementary data types to get a lightweight component
+	private Boolean autoplay;
+
+	private Boolean loop;
+
+	private Boolean muted;
+
+	private Boolean controls;
+
+	private Preload preload;
+
+	private String startTime;
+
+	private String endTime;
+
+	private String mediaGroup;
+
+	private Cors crossorigin;
+
+	private PageParameters pageParameters;
+
+	private MediaStreamingResourceReference mediaStreamingResourceReference;
+
+	private String url;
+
+	public MediaComponent(String id)
+	{
+		super(id);
+	}
+
+	public MediaComponent(String id, IModel<?> model)
+	{
+		super(id, model);
+	}
+
+	public MediaComponent(String id, MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		this(id);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+	}
+
+	public MediaComponent(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		this(id, model);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+	}
+
+	public MediaComponent(String id,
+		MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		this(id);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public MediaComponent(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		this(id, model);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public MediaComponent(String id, String url)
+	{
+		this(id);
+		this.url = url;
+	}
+
+	public MediaComponent(String id, IModel<?> model, String url)
+	{
+		this(id, model);
+		this.url = url;
+	}
+
+	public MediaComponent(String id, String url, PageParameters pageParameters)
+	{
+		this(id);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	public MediaComponent(String id, IModel<?> model, String url, PageParameters pageParameters)
+	{
+		this(id, model);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	@Override
+	protected void onComponentTag(ComponentTag tag)
+	{
+		super.onComponentTag(tag);
+		// The time management is used to set the start / stop
+		// time in seconds of the movie to be played back
+		String timeManagement = "";
+		if (this.startTime != null)
+		{
+			timeManagement = timeManagement += "#t=" + this.startTime +
+				(this.endTime != null ? "," + this.endTime : "");
+		}
+
+		if (this.mediaStreamingResourceReference != null)
+		{
+			tag.put("src",
+				RequestCycle.get()
+					.urlFor(this.mediaStreamingResourceReference, this.pageParameters) +
+					timeManagement);
+		}
+
+		if (this.url != null)
+		{
+			tag.put("src", this.url + timeManagement);
+		}
+
+		if (this.mediaGroup != null)
+		{
+			tag.put("mediagroup", this.mediaGroup);
+		}
+
+		if (this.autoplay != null && this.autoplay)
+		{
+			tag.put("autoplay", "autoplay");
+		}
+
+		if (this.loop != null && this.loop)
+		{
+			tag.put("loop", "loop");
+		}
+
+		if (this.muted != null && this.muted)
+		{
+			tag.put("muted", "muted");
+		}
+
+		// Use getter here because controls should be visible by default
+		if (this.getControls())
+		{
+			tag.put("controls", "controls");
+		}
+
+		if (this.preload != null)
+		{
+			tag.put("preload", this.preload.name());
+		}
+
+		if (this.crossorigin != null)
+		{
+			tag.put("crossorigin", this.crossorigin.getRealName());
+		}
+	}
+
+	/**
+	 * If the playback is autoplayed on load
+	 * 
+	 * @return If the playback is autoplayed on load
+	 */
+	public Boolean getAutoplay()
+	{
+		return this.autoplay != null ? this.autoplay : false;
+	}
+
+	/**
+	 * Sets the playback to be autoplayed on load
+	 * 
+	 * @param autoplay
+	 *            If the playback is autoplayed on load
+	 */
+	public void setAutoplay(Boolean autoplay)
+	{
+		this.autoplay = autoplay;
+	}
+
+	/**
+	 * If the playback is looped
+	 * 
+	 * @return If the playback is looped
+	 */
+	public Boolean getLoop()
+	{
+		return this.loop != null ? this.loop : false;
+	}
+
+	/**
+	 * Sets the playback to be looped
+	 * 
+	 * @param loop
+	 *            If the playback is looped
+	 */
+	public void setLoop(Boolean loop)
+	{
+		this.loop = loop;
+	}
+
+	/**
+	 * If the playback is muted initially
+	 * 
+	 * @return If the playback is muted initially
+	 */
+	public Boolean getMuted()
+	{
+		return this.muted != null ? this.muted : false;
+	}
+
+	/**
+	 * Sets the playback muted initially
+	 * 
+	 * @param muted
+	 *            If the playback is muted initially
+	 */
+	public void setMuted(Boolean muted)
+	{
+		this.muted = muted;
+	}
+
+	/**
+	 * If the controls are going to be displayed
+	 * 
+	 * @return if the controls are going to displayed
+	 */
+	public Boolean getControls()
+	{
+		return this.controls != null ? this.controls : true;
+	}
+
+	/**
+	 * Sets if the controls are going to be displayed
+	 * 
+	 * @param controls
+	 *            if the controls are going to displayed
+	 */
+	public void setControls(Boolean controls)
+	{
+		this.controls = controls;
+	}
+
+	/**
+	 * The type of preload
+	 * 
+	 * @see {@link #setPreload(Preload)}
+	 * 
+	 * @return the preload
+	 */
+	public Preload getPreload()
+	{
+		return this.preload;
+	}
+
+	/**
+	 * Sets the type of preload <br>
+	 * <br>
+	 * <b>none</b>: Hints to the user agent that either the author does not expect the user to need
+	 * the media resource, or that the server wants to minimise unnecessary traffic.<br>
+	 * <br>
+	 * <b>metadata</b>: Hints to the user agent that the author does not expect the user to need the
+	 * media resource, but that fetching the resource metadata (dimensions, first frame, track list,
+	 * duration, etc) is reasonable.<br>
+	 * <br>
+	 * <b>auto</b>: Hints to the user agent that the user agent can put the user's needs first
+	 * without risk to the server, up to and including optimistically downloading the entire
+	 * resource.
+	 * 
+	 * @param preload
+	 *            the preload
+	 */
+	public void setPreload(Preload preload)
+	{
+		this.preload = preload;
+	}
+
+	/**
+	 * Gets the position at which the media component starts the playback
+	 * 
+	 * @see {@link #setStartTime(String)}
+	 * 
+	 * @return the time at which position the media component starts the playback
+	 */
+	public String getStartTime()
+	{
+		return this.startTime;
+	}
+
+	/**
+	 * Sets the position at which the media component starts the playback<br>
+	 * <br>
+	 * t=<b>10</b>,20<br>
+	 * t=<b>npt:10</b>,20<br>
+	 * <br>
+	 * 
+	 * t=<b>120s</b>,121.5s<br>
+	 * t=<b>npt:120</b>,0:02:01.5<br>
+	 * <br>
+	 * 
+	 * t=<b>smpte-30:0:02:00</b>,0:02:01:15<br>
+	 * t=<b>smpte-25:0:02:00:00</b>,0:02:01:12.1<br>
+	 * <br>
+	 * 
+	 * t=<b>clock:20090726T111901Z</b>,20090726T121901Z
+	 * 
+	 * @param startTime
+	 *            the time at which position the media component starts the playback
+	 */
+	public void setStartTime(String startTime)
+	{
+		this.startTime = startTime;
+	}
+
+	/**
+	 * Gets the position at which the media component stops the playback
+	 * 
+	 * @see {@link #setEndTime(String)}
+	 * 
+	 * @return the time at which position the media component stops the playback
+	 */
+	public String getEndTime()
+	{
+		return this.endTime;
+	}
+
+	/**
+	 * Sets the position at which the media component stops the playback<br>
+	 * <br>
+	 * t=10,<b>20</b><br>
+	 * t=npt:10,<b>20</b><br>
+	 * <br>
+	 * 
+	 * t=120s,<b>121.5s</b><br>
+	 * t=npt:120,<b>0:02:01.5</b><br>
+	 * <br>
+	 * 
+	 * t=smpte-30:0:02:00,<b>0:02:01:15</b><br>
+	 * t=smpte-25:0:02:00:00,<b>0:02:01:12.1</b><br>
+	 * <br>
+	 * 
+	 * t=clock:20090726T111901Z,<b>20090726T121901Z</b>
+	 * 
+	 * @param endTime
+	 *            the time at which position the media component stops the playback
+	 */
+	public void setEndTime(String endTime)
+	{
+		this.endTime = endTime;
+	}
+
+	/**
+	 * Gets the media group.
+	 * 
+	 * @return the media group
+	 */
+	public String getMediaGroup()
+	{
+		return this.mediaGroup;
+	}
+
+	/**
+	 * Sets the media group
+	 * 
+	 * @param mediaGroup
+	 *            to be set
+	 */
+	public void setMediaGroup(String mediaGroup)
+	{
+		this.mediaGroup = mediaGroup;
+	}
+
+	/**
+	 * Gets the cross origin settings
+	 * 
+	 * @see {@link #setCrossorigin(Cors)}
+	 * 
+	 * @return the cross origins settings
+	 */
+	public Cors getCrossorigin()
+	{
+		return this.crossorigin;
+	}
+
+	/**
+	 * Sets the cross origin settings<br>
+	 * <br>
+	 * 
+	 * <b>anonymous</b>: Cross-origin CORS requests for the element will not have the credentials
+	 * flag set.<br>
+	 * <br>
+	 * <b>use_credentials</b>: Cross-origin CORS requests for the element will have the credentials
+	 * flag set.<br>
+	 * <br>
+	 * <b>no_cores</b>: The empty string is also a valid keyword, and maps to the Anonymous state.
+	 * The attribute's invalid value default is the Anonymous state. The missing value default, used
+	 * when the attribute is omitted, is the No CORS state
+	 * 
+	 * @param crossorigin
+	 *            the cross origins settings to set
+	 */
+	public void setCrossorigin(Cors crossorigin)
+	{
+		this.crossorigin = crossorigin;
+	}
+
+	/**
+	 * To be used for the preload attribute
+	 * 
+	 * @see {@link #setPreload(Preload)}
+	 */
+	public enum Preload
+	{
+		none, metadata, auto
+	}
+
+	/**
+	 * To be used for the crossorigin attribute
+	 * 
+	 * @see {@link #setCrossorigin(Cors)}
+	 */
+	public enum Cors
+	{
+		anonymous("anonymous"), use_credentials("user-credentials"), no_cors("");
+
+		private String realName;
+
+		private Cors(String realName)
+		{
+			this.realName = realName;
+		}
+
+		public String getRealName()
+		{
+			return this.realName;
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaStreamingResourceReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaStreamingResourceReference.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaStreamingResourceReference.java
new file mode 100755
index 0000000..0051c8c
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaStreamingResourceReference.java
@@ -0,0 +1,306 @@
+package org.apache.wicket.markup.html.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Locale;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.core.util.resource.PackageResourceStream;
+import org.apache.wicket.protocol.http.servlet.ResponseIOException;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.apache.wicket.request.resource.ContentDisposition;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * The media streaming resource reference is used to provided streamed data based on bytes requested
+ * by the client for video and audio files
+ * 
+ * @author Tobias Soloschenko
+ */
+public class MediaStreamingResourceReference extends ResourceReference
+{
+
+	private static final long serialVersionUID = 1L;
+
+	private Integer buffer;
+
+	public MediaStreamingResourceReference(Class<?> scope, String name, Locale locale,
+		String style, String variation)
+	{
+		super(scope, name, locale, style, variation);
+	}
+
+	public MediaStreamingResourceReference(Class<?> scope, String name)
+	{
+		super(scope, name, RequestCycle.get().getRequest().getLocale(), null, null);
+	}
+
+	public MediaStreamingResourceReference(Key key)
+	{
+		super(key);
+	}
+
+	public MediaStreamingResourceReference(String name)
+	{
+		super(name);
+	}
+
+	@Override
+	public IResource getResource()
+	{
+		AbstractResource mediaStreamingResource = new AbstractResource()
+		{
+			private static final long serialVersionUID = 1L;
+			private Long startbyte;
+			private Long endbyte;
+			private PackageResourceStream packageResourceStream;
+
+			@Override
+			protected ResourceResponse newResourceResponse(Attributes attributes)
+			{
+				try
+				{
+					Request request = attributes.getRequest();
+					Response response = attributes.getResponse();
+					if (request instanceof WebRequest && response instanceof WebResponse)
+					{
+						WebRequest webRequest = (WebRequest)request;
+						WebResponse webResponse = (WebResponse)response;
+
+						this.packageResourceStream = new PackageResourceStream(
+							MediaStreamingResourceReference.this.getScope(),
+							MediaStreamingResourceReference.this.getName(),
+							MediaStreamingResourceReference.this.getLocale(),
+							MediaStreamingResourceReference.this.getStyle(),
+							MediaStreamingResourceReference.this.getVariation());
+						long length = this.packageResourceStream.length().bytes();
+
+						ResourceResponse resourceResponse = new ResourceResponse();
+						resourceResponse.setContentType(this.packageResourceStream.getContentType());
+						resourceResponse.setFileName(MediaStreamingResourceReference.this.getName());
+						resourceResponse.setContentDisposition(ContentDisposition.ATTACHMENT);
+						resourceResponse.setLastModified(this.packageResourceStream.lastModifiedTime());
+
+						// We accept ranges, so that the player can
+						// load and play content from a specific byte position
+						webResponse.setHeader("Accept-Range", "bytes");
+
+						// Calculating the response code and the byte range to be played
+						String rangeHeader = webRequest.getHeader("range");
+						if (rangeHeader == null || "".equals(rangeHeader))
+						{
+							resourceResponse.setStatusCode(200);
+							resourceResponse.setContentLength(length);
+						}
+						else
+						{
+							rangeHeader = rangeHeader.replaceAll(" ", "");
+							// If the range header is filled 206 for
+							// partial content has to be returned
+							resourceResponse.setStatusCode(206);
+
+							// And now the calculation of the range to be read
+							// and to be given as response within the Content-Range header
+							String range = rangeHeader.substring(rangeHeader.indexOf('=') + 1,
+								rangeHeader.length());
+							String[] rangeParts = range.split("-");
+							if (rangeParts[0].equals("0"))
+							{
+								webResponse.setHeader("Content-Range", "bytes 0-" + (length - 1) +
+									"/" + length);
+								resourceResponse.setContentLength(length);
+							}
+							else
+							{
+								this.startbyte = Long.parseLong(rangeParts[0]);
+								if (rangeParts.length == 2)
+								{
+									this.endbyte = Long.parseLong(rangeParts[1]);
+								}
+								else
+								{
+									this.endbyte = length - 1;
+								}
+								webResponse.setHeader("Content-Range", "bytes " + this.startbyte +
+									"-" + this.endbyte + "/" + length);
+								resourceResponse.setContentLength((this.endbyte - this.startbyte) + 1);
+							}
+						}
+
+						resourceResponse.setWriteCallback(new WriteCallback()
+						{
+							@Override
+							public void writeData(Attributes attributes) throws IOException
+							{
+								try
+								{
+									InputStream inputStream = packageResourceStream.getInputStream();
+									OutputStream outputStream = attributes.getResponse()
+										.getOutputStream();
+									byte[] buffer = new byte[MediaStreamingResourceReference.this.getBuffer()];
+
+									if (startbyte != null || endbyte != null)
+									{
+										// skipping the first bytes which are
+										// not requested by the client
+										inputStream.skip(startbyte);
+
+										long totalBytes = 0;
+										int actualReadBytes = 0;
+
+										while ((actualReadBytes = inputStream.read(buffer)) != -1)
+										{
+											totalBytes = totalBytes + buffer.length;
+											long lowerBuffer = endbyte - totalBytes;
+											if (lowerBuffer <= 0)
+											{
+												buffer = (byte[])resizeArray(buffer,
+													actualReadBytes);
+												outputStream.write(buffer);
+												break;
+											}
+											else
+											{
+												outputStream.write(buffer);
+											}
+										}
+									}
+									else
+									{
+										while (inputStream.read(buffer) != -1)
+										{
+											outputStream.write(buffer);
+										}
+									}
+								}
+								catch (ResponseIOException e)
+								{
+									// the client has closed the connection and
+									// doesn't read the stream further on
+									// (in tomcats
+									// org.apache.catalina.connector.ClientAbortException)
+									// we ignore this case
+								}
+								catch (Exception e)
+								{
+									StringWriter stringWriter = printStack(e);
+									throw new WicketRuntimeException(stringWriter.toString());
+								}
+							}
+						});
+
+						return resourceResponse;
+					}
+					else
+					{
+						throw new IllegalStateException(
+							"Either the request is no web request or the response is no web response");
+					}
+				}
+				catch (Exception e)
+				{
+					StringWriter stringWriter = this.printStack(e);
+					throw new WicketRuntimeException(stringWriter.toString());
+				}
+				finally
+				{
+					if (this.packageResourceStream != null)
+					{
+						try
+						{
+							this.packageResourceStream.close();
+						}
+						catch (IOException e)
+						{
+							StringWriter stringWriter = this.printStack(e);
+							throw new WicketRuntimeException(stringWriter.toString());
+						}
+					}
+				}
+			}
+
+			/**
+			 * Prints the stack trace to a print writer
+			 * 
+			 * @param exception
+			 *            the exception
+			 * @return the string writer containing the stack trace
+			 */
+			private StringWriter printStack(Exception exception)
+			{
+				StringWriter stringWriter = new StringWriter();
+				PrintWriter printWriter = new PrintWriter(stringWriter);
+				exception.printStackTrace(printWriter);
+				return stringWriter;
+			}
+		};
+		return mediaStreamingResource;
+
+	}
+
+	/**
+	 * Sets the buffer size used to send the data to the client
+	 * 
+	 * @return the buffer size used to send the data to the client
+	 */
+	public Integer getBuffer()
+	{
+		return this.buffer != null ? this.buffer : 4048;
+	}
+
+	/**
+	 * Sets the buffer size used to send the data to the client
+	 * 
+	 * @param buffer
+	 *            the buffer size used to send the data to the client
+	 */
+	public void setBuffer(Integer buffer)
+	{
+		this.buffer = buffer;
+	}
+
+	/**
+	 * Reallocates an array with a new size, and copies the contents of the old array to the new
+	 * array.
+	 * 
+	 * @param oldArray
+	 *            the old array, to be reallocated.
+	 * @param newSize
+	 *            the new array size.
+	 * @return A new array with the same contents.
+	 */
+	@SuppressWarnings("rawtypes")
+	private static Object resizeArray(Object oldArray, int newSize)
+	{
+		int oldSize = java.lang.reflect.Array.getLength(oldArray);
+		Class elementType = oldArray.getClass().getComponentType();
+		Object newArray = java.lang.reflect.Array.newInstance(elementType, newSize);
+		int preserveLength = Math.min(oldSize, newSize);
+		if (preserveLength > 0)
+		{
+			System.arraycopy(oldArray, 0, newArray, 0, preserveLength);
+		}
+		return newArray;
+	}
+
+	/**
+	 * Gets the type of the media this resource reference belongs to
+	 * 
+	 * @return the type of this media
+	 */
+	public String getType()
+	{
+		final String resourceName = MediaStreamingResourceReference.this.getName();
+		return Application.get().getMimeType(resourceName);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaUtils.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaUtils.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaUtils.java
new file mode 100755
index 0000000..d06a704
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/MediaUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.markup.html.IPackageResourceGuard;
+import org.apache.wicket.markup.html.SecurePackageResourceGuard;
+
+/**
+ * Helper method to provide access to basic media files like subtitles
+ * 
+ * @author Tobias Soloschenko
+ * 
+ */
+public class MediaUtils
+{
+
+	/**
+	 * Method that has to be called within the init method of the web application to make
+	 * translation files accessible
+	 */
+	public static void init()
+	{
+		IPackageResourceGuard packageResourceGuard = Application.get()
+			.getResourceSettings()
+			.getPackageResourceGuard();
+		if (packageResourceGuard instanceof SecurePackageResourceGuard)
+		{
+			SecurePackageResourceGuard securePackageResourceGuard = (SecurePackageResourceGuard)packageResourceGuard;
+			securePackageResourceGuard.addPattern("+*.vtt");
+			securePackageResourceGuard.addPattern("+*.srt");
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Source.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Source.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Source.java
new file mode 100755
index 0000000..0963f06
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Source.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * The source of an audio or a video media component
+ * 
+ * @author Tobias Soloschenko
+ * @author Andrew Lombardi
+ * 
+ */
+public class Source extends WebMarkupContainer
+{
+
+	private static final long serialVersionUID = 1L;
+
+	private Boolean displayType;
+
+	private String type;
+
+	private String media;
+
+	private MediaStreamingResourceReference mediaStreamingResourceReference;
+
+	private PageParameters pageParameters;
+
+	private String url;
+
+	public Source(String id)
+	{
+		super(id);
+	}
+
+	public Source(String id, IModel<?> model)
+	{
+		super(id, model);
+	}
+
+	public Source(String id, MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		this(id);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+	}
+
+	public Source(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		this(id, model);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+	}
+
+	public Source(String id, MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		this(id);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public Source(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		this(id, model);
+		this.mediaStreamingResourceReference = mediaStreamingResourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public Source(String id, String url)
+	{
+		this(id);
+		this.url = url;
+	}
+
+	public Source(String id, IModel<?> model, String url)
+	{
+		this(id, model);
+		this.url = url;
+	}
+
+	public Source(String id, String url, PageParameters pageParameters)
+	{
+		this(id);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	public Source(String id, IModel<?> model, String url, PageParameters pageParameters)
+	{
+		this(id, model);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	@Override
+	protected void onComponentTag(ComponentTag tag)
+	{
+		this.checkComponentTag(tag, "source");
+		super.onComponentTag(tag);
+
+		if (this.mediaStreamingResourceReference != null)
+		{
+			tag.put("src",
+				RequestCycle.get()
+					.urlFor(this.mediaStreamingResourceReference, this.pageParameters));
+		}
+
+		if (this.url != null)
+		{
+			tag.put("src", this.url);
+		}
+
+		if (this.getDisplayType())
+		{
+			if (this.type != null)
+			{
+				tag.put("type", this.type);
+			}
+			else if (this.mediaStreamingResourceReference != null)
+			{
+				tag.put("type", this.mediaStreamingResourceReference.getType());
+			}
+		}
+
+		if (this.media != null)
+		{
+			tag.put("media", this.media);
+		}
+
+	}
+
+	/**
+	 * If the type is going to be displayed
+	 * 
+	 * @return If the type is going to be displayed
+	 */
+	public Boolean getDisplayType()
+	{
+		return this.displayType != null ? this.displayType : false;
+	}
+
+	/**
+	 * Sets if the type is going to be displayed
+	 * 
+	 * @param displayType
+	 *            if the type is going to be displayed
+	 */
+	public void setDisplayType(Boolean displayType)
+	{
+		this.displayType = displayType;
+	}
+
+	/**
+	 * Gets the type
+	 * 
+	 * @see {@link #setType(String)}
+	 * 
+	 * @return the type of this media element
+	 */
+	public String getType()
+	{
+		return this.type;
+	}
+
+	/**
+	 * Sets the type<br>
+	 * <br>
+	 * 
+	 * * The following list shows some examples of how to use the codecs= MIME parameter in the type
+	 * attribute.<br>
+	 * <br>
+	 * 
+	 * H.264 Constrained baseline profile video (main and extended video compatible) level 3 and
+	 * Low-Complexity AAC audio in MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'</b>&gt;<br>
+	 * H.264 Extended profile video (baseline-compatible) level 3 and Low-Complexity AAC audio in
+	 * MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="avc1.58A01E, mp4a.40.2"'</b>&gt;<br>
+	 * H.264 Main profile video level 3 and Low-Complexity AAC audio in MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'</b>&gt;<br>
+	 * H.264 'High' profile video (incompatible with main, baseline, or extended profiles) level 3
+	 * and Low-Complexity AAC audio in MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="avc1.64001E, mp4a.40.2"'</b>&gt;<br>
+	 * MPEG-4 Visual Simple Profile Level 0 video and Low-Complexity AAC audio in MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="mp4v.20.8, mp4a.40.2"'</b>&gt;<br>
+	 * MPEG-4 Advanced Simple Profile Level 0 video and Low-Complexity AAC audio in MP4 container<br>
+	 * &lt;source src='video.mp4' <b>type='video/mp4; codecs="mp4v.20.240, mp4a.40.2"'</b>&gt;<br>
+	 * MPEG-4 Visual Simple Profile Level 0 video and AMR audio in 3GPP container<br>
+	 * &lt;source src='video.3gp' <b>type='video/3gpp; codecs="mp4v.20.8, samr"'</b>&gt;<br>
+	 * Theora video and Vorbis audio in Ogg container<br>
+	 * &lt;source src='video.ogv' <b>type='video/ogg; codecs="theora, vorbis"'</b>&gt;<br>
+	 * Theora video and Speex audio in Ogg container<br>
+	 * &lt;source src='video.ogv' <b>type='video/ogg; codecs="theora, speex"'</b>&gt;<br>
+	 * Vorbis audio alone in Ogg container<br>
+	 * &lt;source src='audio.ogg' <b>type='audio/ogg; codecs=vorbis'</b>&gt;<br>
+	 * Speex audio alone in Ogg container<br>
+	 * &lt;source src='audio.spx' <b>type='audio/ogg; codecs=speex'</b>&gt;<br>
+	 * FLAC audio alone in Ogg container<br>
+	 * &lt;source src='audio.oga' <b>type='audio/ogg; codecs=flac'</b>&gt;<br>
+	 * Dirac video and Vorbis audio in Ogg container<br>
+	 * &lt;source src='video.ogv' <b>type='video/ogg; codecs="dirac, vorbis"'</b>&gt;<br>
+	 * Theora video and Vorbis audio in Matroska container<br>
+	 * &lt;source src='video.mkv' <b>type='video/x-matroska; codecs="theora, vorbis"'</b>&gt;<br>
+	 * 
+	 * @param type
+	 *            the type of this media element
+	 */
+	public void setType(String type)
+	{
+		this.type = type;
+	}
+
+	/**
+	 * The media for which the content of this source should be shown
+	 * 
+	 * @See {@link #setMedia(String)}
+	 * @return The media for which the content of this source should be shown
+	 */
+	public String getMedia()
+	{
+		return this.media;
+	}
+
+	/**
+	 * Sets the media for which the content of this source should be shown<br>
+	 * <br>
+	 * 
+	 * &lt;source src="movie.ogg" type="video/ogg" <b>media="screen and (min-width:320px)"&gt;<br>
+	 * 
+	 * @param media
+	 *            the media for which to content of this source should be shown
+	 */
+	public void setMedia(String media)
+	{
+		this.media = media;
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Track.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Track.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Track.java
new file mode 100755
index 0000000..587ffcd
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/Track.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import java.util.Locale;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * The track tag is used to provide subtitles, captions, descriptions, chapters, metadata to a video
+ * media component
+ * 
+ * @author Tobias Soloschenko
+ * 
+ */
+public class Track extends WebMarkupContainer
+{
+
+	private static final long serialVersionUID = 1L;
+
+	private Kind kind;
+
+	private String label;
+
+	private Boolean defaultTrack;
+
+	private Locale srclang;
+
+	private ResourceReference resourceReference;
+
+	private String url;
+
+	private PageParameters pageParameters;
+
+	public Track(String id)
+	{
+		super(id);
+	}
+
+	public Track(String id, IModel<?> model)
+	{
+		super(id, model);
+	}
+
+	public Track(String id, ResourceReference resourceReference)
+	{
+		this(id);
+		this.resourceReference = resourceReference;
+	}
+
+	public Track(String id, IModel<?> model, ResourceReference resourceReference)
+	{
+		this(id, model);
+		this.resourceReference = resourceReference;
+	}
+
+	public Track(String id, ResourceReference resourceReference, PageParameters pageParameters)
+	{
+		this(id);
+		this.resourceReference = resourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public Track(String id, IModel<?> model, ResourceReference resourceReference,
+		PageParameters pageParameters)
+	{
+		this(id, model);
+		this.resourceReference = resourceReference;
+		this.pageParameters = pageParameters;
+	}
+
+	public Track(String id, String url)
+	{
+		this(id);
+		this.url = url;
+	}
+
+	public Track(String id, IModel<?> model, String url)
+	{
+		this(id, model);
+		this.url = url;
+	}
+
+	public Track(String id, String url, PageParameters pageParameters)
+	{
+		this(id);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	public Track(String id, IModel<?> model, String url, PageParameters pageParameters)
+	{
+		this(id, model);
+		this.url = url;
+		this.pageParameters = pageParameters;
+	}
+
+	@Override
+	protected void onComponentTag(ComponentTag tag)
+	{
+		this.checkComponentTag(tag, "track");
+		super.onComponentTag(tag);
+
+		if (this.resourceReference != null)
+		{
+			tag.put("src", RequestCycle.get().urlFor(this.resourceReference, this.pageParameters));
+		}
+
+		if (this.url != null)
+		{
+			tag.put("src", this.url);
+		}
+
+		if (this.kind != null)
+		{
+			tag.put("kind", this.kind.name());
+		}
+
+		if (this.label != null)
+		{
+			tag.put("label", this.label);
+		}
+
+		if (this.defaultTrack != null && this.defaultTrack)
+		{
+			tag.put("default", "default");
+		}
+
+		// if the srclang field is set use this, else if the
+		// resource reference provides a locale use the language
+		// of the resource reference
+		if (this.srclang != null)
+		{
+			tag.put("srclang", this.srclang.getLanguage());
+		}
+		else if (this.resourceReference != null && this.resourceReference.getLocale() != null)
+		{
+			tag.put("srclang", this.resourceReference.getLocale().getLanguage());
+		}
+	}
+
+	/**
+	 * Gets the kind of the track belongs to the media component
+	 * 
+	 * @see {@link #setKind(Kind)}
+	 * 
+	 * @return the kind
+	 */
+	public Kind getKind()
+	{
+		return this.kind;
+	}
+
+	/**
+	 * Sets the kind of the track belongs to the media component<br>
+	 * <br>
+	 * <b>subtitles</b>: Transcription or translation of the dialogue, suitable for when the sound
+	 * is available but not understood (e.g. because the user does not understand the language of
+	 * the media resource's soundtrack). Displayed over the video.<br>
+	 * <br>
+	 * <b>captions</b>: Transcription or translation of the dialogue, sound effects, relevant
+	 * musical cues, and other relevant audio information, suitable for when the soundtrack is
+	 * unavailable (e.g. because it is muted or because the user is deaf). Displayed over the video;
+	 * labeled as appropriate for the hard-of-hearing.<br>
+	 * <br>
+	 * <b>descriptions</b>: Textual descriptions of the video component of the media resource,
+	 * intended for audio synthesis when the visual component is unavailable (e.g. because the user
+	 * is interacting with the application without a screen while driving, or because the user is
+	 * blind). Synthesized as separate audio track.<br>
+	 * <br>
+	 * <b>chapters</b>: Chapter titles, intended to be used for navigating the media resource.
+	 * Displayed as an interactive list in the user agent's interface.<br>
+	 * <br>
+	 * <b>metadata</b>: Tracks intended for use from script. Not displayed by the user agent.<br>
+	 * <br>
+	 * 
+	 * @param the
+	 *            kind
+	 */
+	public void setKind(Kind kind)
+	{
+		this.kind = kind;
+	}
+
+	/**
+	 * The label for this track
+	 * 
+	 * @return the label
+	 */
+	public String getLabel()
+	{
+		return this.label;
+	}
+
+	/**
+	 * Sets the label for this track
+	 * 
+	 * @param label
+	 *            the label to be set
+	 */
+	public void setLabel(String label)
+	{
+		this.label = label;
+	}
+
+	/**
+	 * If the track is the default track
+	 * 
+	 * @return if the track is the default track
+	 */
+	public Boolean getDefaultTrack()
+	{
+		return this.defaultTrack != null ? this.defaultTrack : false;
+	}
+
+	/**
+	 * Sets if this track is the default track
+	 * 
+	 * @param defaultTrack
+	 *            if the track is the default track
+	 */
+	public void setDefaultTrack(Boolean defaultTrack)
+	{
+		this.defaultTrack = defaultTrack;
+	}
+
+	/**
+	 * Gets the src lang
+	 * 
+	 * @return the src lang
+	 */
+	public Locale getSrclang()
+	{
+		return this.srclang;
+	}
+
+	/**
+	 * Sets the src lang
+	 * 
+	 * @param srclang
+	 *            the src lang to set
+	 */
+	public void setSrclang(Locale srclang)
+	{
+		this.srclang = srclang;
+	}
+
+	/**
+	 * To be used for the kind attribute
+	 */
+	public enum Kind
+	{
+		subtitles, captions, descriptions, chapters, metadata
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/audio/Audio.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/audio/Audio.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/audio/Audio.java
new file mode 100755
index 0000000..d1f0fe2
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/audio/Audio.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media.audio;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.media.MediaComponent;
+import org.apache.wicket.markup.html.media.MediaStreamingResourceReference;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * An audio media component to playback audio files.
+ * 
+ * @author Tobias Soloschenko
+ * @author Andrew Lombardi
+ * 
+ */
+public class Audio extends MediaComponent
+{
+
+	private static final long serialVersionUID = 1L;
+
+	public Audio(String id)
+	{
+		super(id);
+	}
+
+	public Audio(String id, IModel<?> model)
+	{
+		super(id, model);
+	}
+
+	public Audio(String id, MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		super(id, mediaStreamingResourceReference);
+	}
+
+	public Audio(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		super(id, model, mediaStreamingResourceReference);
+	}
+
+	public Audio(String id, MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		super(id, mediaStreamingResourceReference, pageParameters);
+	}
+
+	public Audio(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		super(id, model, mediaStreamingResourceReference, pageParameters);
+	}
+
+	public Audio(String id, String url)
+	{
+		super(id, url);
+	}
+
+	public Audio(String id, IModel<?> model, String url)
+	{
+		super(id, model, url);
+	}
+
+	public Audio(String id, String url, PageParameters pageParameters)
+	{
+		super(id, url, pageParameters);
+	}
+
+	public Audio(String id, IModel<?> model, String url, PageParameters pageParameters)
+	{
+		super(id, model, url, pageParameters);
+	}
+
+	@Override
+	protected void onComponentTag(ComponentTag tag)
+	{
+		this.checkComponentTag(tag, "audio");
+		super.onComponentTag(tag);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/main/java/org/apache/wicket/markup/html/media/video/Video.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/media/video/Video.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/video/Video.java
new file mode 100755
index 0000000..c463a07
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/media/video/Video.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media.video;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.media.MediaComponent;
+import org.apache.wicket.markup.html.media.MediaStreamingResourceReference;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * A video media component to display videos.
+ * 
+ * @author Tobias Soloschenko
+ * @author Andrew Lombardi
+ */
+public class Video extends MediaComponent
+{
+
+	private static final long serialVersionUID = 1L;
+
+	private Integer width;
+
+	private Integer height;
+
+	private ResourceReference poster;
+
+	private PageParameters posterPageParameters;
+
+	public Video(String id)
+	{
+		super(id);
+	}
+
+	public Video(String id, IModel<?> model)
+	{
+		super(id, model);
+	}
+
+	public Video(String id, MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		super(id, mediaStreamingResourceReference);
+	}
+
+	public Video(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference)
+	{
+		super(id, model, mediaStreamingResourceReference);
+	}
+
+	public Video(String id, MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		super(id, mediaStreamingResourceReference, pageParameters);
+	}
+
+	public Video(String id, IModel<?> model,
+		MediaStreamingResourceReference mediaStreamingResourceReference,
+		PageParameters pageParameters)
+	{
+		super(id, model, mediaStreamingResourceReference, pageParameters);
+	}
+
+	public Video(String id, String url)
+	{
+		super(id, url);
+	}
+
+	public Video(String id, IModel<?> model, String url)
+	{
+		super(id, model, url);
+	}
+
+	public Video(String id, String url, PageParameters pageParameters)
+	{
+		super(id, url, pageParameters);
+	}
+
+	public Video(String id, IModel<?> model, String url, PageParameters pageParameters)
+	{
+		super(id, model, url, pageParameters);
+	}
+
+	@Override
+	protected void onComponentTag(ComponentTag tag)
+	{
+		this.checkComponentTag(tag, "video");
+		super.onComponentTag(tag);
+
+		if (this.width != null)
+		{
+			tag.put("width", this.width);
+		}
+
+		if (this.height != null)
+		{
+			tag.put("height", this.height);
+		}
+
+		if (this.poster != null)
+		{
+			tag.put("poster", RequestCycle.get().urlFor(this.poster, this.posterPageParameters));
+		}
+	}
+
+	/**
+	 * The image to be displayed if the video isn't available
+	 * 
+	 * @return the resource reference of the image
+	 */
+	public ResourceReference getPoster()
+	{
+		return this.poster;
+	}
+
+	/**
+	 * Gets the posters page parameters
+	 * 
+	 * @return the page parameters for the poster
+	 */
+	public PageParameters getPosterPageParameters()
+	{
+		return this.posterPageParameters;
+	}
+
+	/**
+	 * Sets the image to be displayed if the video isn't available
+	 * 
+	 * @param poster
+	 *            the resource reference of the image used if the video isn't available
+	 */
+	public void setPoster(ResourceReference poster)
+	{
+		this.poster = poster;
+	}
+
+	/**
+	 * Sets the image to be displayed if the video isn't available
+	 * 
+	 * @param poster
+	 *            the resource reference of the image used if the video isn't available
+	 * @param posterPageParameters
+	 *            the page parameters for the poster
+	 */
+	public void setPoster(ResourceReference poster, PageParameters posterPageParameters)
+	{
+		this.poster = poster;
+		this.posterPageParameters = posterPageParameters;
+	}
+
+	/**
+	 * Gets the width of the video area
+	 * 
+	 * @return the width of the video area
+	 */
+	public Integer getWidth()
+	{
+		return this.width;
+	}
+
+	/**
+	 * Sets the width of the video area
+	 * 
+	 * @param width
+	 *            the width of the video area
+	 */
+	public void setWidth(Integer width)
+	{
+		this.width = width;
+	}
+
+	/**
+	 * Gets the height of the video area
+	 * 
+	 * @return the height of the video area
+	 */
+	public Integer getHeight()
+	{
+		return this.height;
+	}
+
+	/**
+	 * Sets the height of the video area
+	 * 
+	 * @param height
+	 *            the height of the video area
+	 */
+	public void setHeight(Integer height)
+	{
+		this.height = height;
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/Application.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/Application.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/Application.java
new file mode 100755
index 0000000..7a15513
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/Application.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.protocol.http.WebApplication;
+
+public class Application extends WebApplication
+{
+
+	@Override
+	public Class<? extends Page> getHomePage()
+	{
+		return MediaTagsTestPage.class;
+	}
+	
+	@Override
+	protected void init()
+	{
+		super.init();
+		MediaUtils.init();
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.html b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.html
new file mode 100755
index 0000000..1f7d793
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>de.test.mediatags</title>
+	</head>
+	<body>
+		<video wicket:id="video">
+			<source wicket:id="source" />
+			<track wicket:id="track" />
+		</video>
+	</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.java
new file mode 100644
index 0000000..3f17ad7
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsExtendedTestPage.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import java.util.Locale;
+
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.media.Track.Kind;
+import org.apache.wicket.markup.html.media.video.Video;
+import org.apache.wicket.request.resource.PackageResourceReference;
+
+public class MediaTagsExtendedTestPage extends WebPage
+{
+
+	private static final long serialVersionUID = 1L;
+
+	public MediaTagsExtendedTestPage()
+	{
+
+		Video video = new Video("video", new MediaStreamingResourceReference(
+			MediaTagsTestPage.class, "dummyVideo.m4a"));
+		
+		// source tag
+		Source source = new Source("source","http://www.mytestpage.xc/video.m4a");
+		source.setMedia("screen and (device-width:500px)");
+		source.setType("video/mp4");
+		source.setDisplayType(true);
+		video.add(source);
+		
+		// tack tag
+		Track track = new Track("track", new PackageResourceReference(MediaTagsTestPage.class,"dummySubtitles.vtt"));
+		track.setKind(Kind.subtitles);
+		track.setLabel("Subtitles of video");
+		track.setSrclang(Locale.GERMANY);
+		track.setDefaultTrack(true);
+		video.add(track);
+		
+		add(video);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTest.java
new file mode 100644
index 0000000..0863365
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.util.tester.TagTester;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MediaTagsTest
+{
+	private WicketTester wicketTester;
+
+	@Before
+	public void setup()
+	{
+		wicketTester = new WicketTester(new Application());
+	}
+
+	@Test
+	public void testAudioTagIsRenderedRight()
+	{
+		wicketTester.startPage(MediaTagsTestPage.class);
+		String lastResponseAsString = wicketTester.getLastResponse().getDocument();
+		TagTester createTagByAttribute = TagTester.createTagByAttribute(lastResponseAsString,
+			"audio");
+		Assert.assertTrue(createTagByAttribute.hasAttribute("autoplay"));
+		Assert.assertTrue(createTagByAttribute.hasAttribute("controls"));
+		Assert.assertTrue(createTagByAttribute.hasAttribute("loop"));
+		Assert.assertTrue(createTagByAttribute.hasAttribute("muted"));
+		Assert.assertEquals("user-credentials", createTagByAttribute.getAttribute("crossorigin"));
+		String attribute = createTagByAttribute.getAttribute("src");
+		Assert.assertTrue("The time period is set right in the src attribute",
+			attribute.contains("#t=5,10"));
+		Assert.assertTrue("page parameter is in the url of the src attribute",
+			attribute.contains("test=test"));
+	}
+
+	@Test
+	public void testVideoTagIsRenderedRight()
+	{
+		wicketTester.startPage(MediaTagsTestPage.class);
+		String lastResponseAsString = wicketTester.getLastResponse().getDocument();
+		TagTester createTagByAttribute = TagTester.createTagByAttribute(lastResponseAsString,
+			"video");
+		String attribute = createTagByAttribute.getAttribute("poster");
+		Assert.assertTrue("page parameter is in the url of the poster",
+			attribute.contains("test2=test2"));
+		String attributesrc = createTagByAttribute.getAttribute("src");
+		Assert.assertTrue("video url is in the src attribute",
+			attributesrc.contains("dummyVideo.m4a"));
+		Assert.assertEquals("500", createTagByAttribute.getAttribute("width"));
+		Assert.assertEquals("400", createTagByAttribute.getAttribute("height"));
+	}
+
+	@Test
+	public void testextendedVideoTagIsRenderedRight()
+	{
+		wicketTester.startPage(MediaTagsExtendedTestPage.class);
+		String lastResponseAsString = wicketTester.getLastResponse().getDocument();
+		TagTester createTagByAttribute = TagTester.createTagByAttribute(lastResponseAsString,
+			"video");
+		Assert.assertTrue(createTagByAttribute.hasChildTag("source"));
+		Assert.assertTrue(createTagByAttribute.hasChildTag("track"));
+		
+		
+		TagTester sourceTag = TagTester.createTagByAttribute(lastResponseAsString, "source");
+		Assert.assertEquals("video/mp4", sourceTag.getAttribute("type")); 
+		Assert.assertEquals("screen and (device-width:500px)", sourceTag.getAttribute("media")); 
+		Assert.assertEquals("http://www.mytestpage.xc/video.m4a", sourceTag.getAttribute("src")); 
+		
+		TagTester trackTag = TagTester.createTagByAttribute(lastResponseAsString, "track");
+		
+		Assert.assertTrue(trackTag.getAttribute("src").contains("dummySubtitles")); 
+		Assert.assertEquals("subtitles", trackTag.getAttribute("kind")); 
+		Assert.assertEquals("Subtitles of video", trackTag.getAttribute("label")); 
+		Assert.assertEquals("default", trackTag.getAttribute("default")); 
+		Assert.assertEquals("de", trackTag.getAttribute("srclang")); 
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.html b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.html
new file mode 100755
index 0000000..e68360d
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>de.test.mediatags</title>
+	</head>
+	<body>
+		<audio wicket:id="audio" />
+		<video wicket:id="video"/>
+	</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.java
new file mode 100644
index 0000000..8393f42
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/MediaTagsTestPage.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.html.media;
+
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.media.MediaComponent.Cors;
+import org.apache.wicket.markup.html.media.audio.Audio;
+import org.apache.wicket.markup.html.media.video.Video;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.PackageResourceReference;
+
+public class MediaTagsTestPage extends WebPage
+{
+
+	private static final long serialVersionUID = 1L;
+
+	public MediaTagsTestPage()
+	{
+		PageParameters pageParameters = new PageParameters();
+		pageParameters.set("test", "test");
+		Audio audio = new Audio("audio", new MediaStreamingResourceReference(
+			MediaTagsTestPage.class, "dummyAudio.mp3"), pageParameters);
+		audio.setAutoplay(true);
+		audio.setControls(true);
+		audio.setCrossorigin(Cors.use_credentials);
+		audio.setLoop(true);
+		audio.setMuted(true);
+		audio.setStartTime("5");
+		audio.setEndTime("10");
+		add(audio);
+
+		Video video = new Video("video", new MediaStreamingResourceReference(
+			MediaTagsTestPage.class, "dummyVideo.m4a"));
+		PageParameters pageParameters2 = new PageParameters();
+		pageParameters2.add("test2", "test2");
+		video.setPoster(new PackageResourceReference(MediaTagsTestPage.class, "dummyPoster.jpg"),
+			pageParameters2);
+		video.setWidth(500);
+		video.setHeight(400);
+		add(video);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyAudio.mp3
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyAudio.mp3 b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyAudio.mp3
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyPoster.jpg
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyPoster.jpg b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyPoster.jpg
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummySubtitles.vtt
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummySubtitles.vtt b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummySubtitles.vtt
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/wicket/blob/29bb9a4d/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyVideo.m4a
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyVideo.m4a b/wicket-core/src/test/java/org/apache/wicket/markup/html/media/dummyVideo.m4a
new file mode 100644
index 0000000..e69de29