new renderings
@@ -0,0 +1,7 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
@@ -0,0 +1,125 @@
|
||||
fancyBox - Changelog
|
||||
=========
|
||||
|
||||
### Version 2.1.5 - June 14, 2013
|
||||
* Fixed #493 - Broken slideshow
|
||||
* Fixed #556 - Parent option
|
||||
* Retina graphics (#564) and retina display support (#420)
|
||||
* Improved "lock" feature
|
||||
|
||||
### Version 2.1.4 - January 10, 2013
|
||||
* Update to be compatible with jQuery v1.9
|
||||
* Small changes that should fix usability issues for certain users
|
||||
|
||||
### Version 2.1.3 - October 23, 2012
|
||||
|
||||
* Fixed #426 - Broken IE7
|
||||
* Fixed #423 - Background flickering on iOS
|
||||
* Fixed #418 - Automatically Grow/Shrink and Center
|
||||
* Updated the script to work with jQuery 1.6
|
||||
* Media helper supports YouTube video series
|
||||
|
||||
### Version 2.1.2 - October 15, 2012
|
||||
|
||||
* Fixed #414 - Don't allow nextClick if there is only one item
|
||||
* Fixed #397 - Button helper 'Menu' not visible in IE7
|
||||
* Overlay can be opened/closed manually:
|
||||
* $.fancybox.helpers.overlay.open();
|
||||
* $.fancybox.helpers.overlay.open({closeClick : false});
|
||||
* $.fancybox.helpers.overlay.close();
|
||||
* Optimized for Internet Explorer 10 (Windows 8)
|
||||
|
||||
### Version 2.1.1 - October 01, 2012
|
||||
|
||||
* Fixed #357 - Converting values like 'auto' in getScalar()
|
||||
* Fixed #358 - Updated overlay background image
|
||||
* New "fancybox-href" and "fancybox-title" HTML5 data-attributes (#317)
|
||||
* Improved helpers:
|
||||
* - now they can have a property 'defaults' that contains default settings
|
||||
* - updated vimeo and youtube parsers for media helper
|
||||
* Content locking now can be turned off
|
||||
|
||||
### Version 2.1.0 - August 20, 2012
|
||||
|
||||
* Fixed #103 - DOM element re-injection after closing
|
||||
* Fixed #188 - navigation keys inside editable content
|
||||
* New animation directions (see https://github.com/fancyapps/fancyBox/issues/233#issuecomment-5512453)
|
||||
* New option "iframe" - it is now possible to separate scrolling for iframe and wrapping element; choose to preload
|
||||
* New option "swf" - brings back functionality from fancyBox v1
|
||||
* Improved media helper - better support for vimeo and youtube; links are now configurable
|
||||
* Rewritten overlay helper:
|
||||
* - new option "showEarly" - toggles if should be open before of after content is loaded
|
||||
* - Facebook-style (https://github.com/fancyapps/fancyBox/issues/24) and therefore uses image for background
|
||||
* Option "padding" accepts array (e.g., padding: [15, 50, 10, 5])
|
||||
* One of dimensions (width or height) can now be set to "auto" (option "autoSize" needs to be "false")
|
||||
* Updated callbacks:
|
||||
* - "beforeClose" is now called only once
|
||||
* - "afterLoad" receives current and previous object as arguments
|
||||
* Method "$.fancybox.update();" recalculates content width/height
|
||||
* Updated to work with jQuery v1.8
|
||||
|
||||
### Version 2.0.6 - April 16, 2012
|
||||
|
||||
* Fixed #188 - keystrokes in contenteditable
|
||||
* Fixed #171 - non-images should not be preloaded
|
||||
* Fixed #158 - 'closeClick: true' breaks gallery navigation
|
||||
* New "media" helper - detects and displays various media types
|
||||
* New option "groupAttr" - name of group selector attribute, default is "data-fancybox-group"
|
||||
* New feature - selector expressions in URLs, see #170
|
||||
* Improved 'overlay' helper to use "position: fixed"
|
||||
* Improved autoSize, fixed wrong height in some cases
|
||||
* Improved centering and iframe scrolling for iOS
|
||||
* Updated markup, new element '.fancybox-skin' is now used for styling
|
||||
|
||||
### Version 2.0.5 - February 21, 2012
|
||||
|
||||
* Fixed #155 - easing for prev/next animations
|
||||
* Fixed #153 - overriding "keys" options
|
||||
* Fixed #147 - IE7 problem with #hash links
|
||||
* Fixed #130 - changing dynamically data-fancybox-group
|
||||
* Fixed #126 - obey minWidth/minHeight
|
||||
* Fixed #118 - placement of loading icon and navigation arrows
|
||||
* Fixed #101 - "index" option not working
|
||||
* Fixed #94 - "orig" option not working
|
||||
* Fixed #80 - does not work on IE6
|
||||
* Fixed #72 - can't set overlay opacity to 0
|
||||
* Fixed #63 - properly set gallery index
|
||||
* New option "autoCenter" - toggles centering on window resize or scroll, disabled for mobile devices by default
|
||||
* New option "autoResize" - toggles responsivity, disabled for mobile devices by default
|
||||
* New option "preload" - number of images to preload
|
||||
* New feature to target mobile/desktop browsers using CSS, see #108
|
||||
* Changed ajax option defaults to "{ dataType: 'html', headers: { 'X-fancyBox': true } }", see #150 and #128
|
||||
* Updated loading icon for IE7, IE8
|
||||
* Calculates height of the iframe if 'autoSize' is set to 'true' and the iframe is on the same domain as the main page
|
||||
|
||||
### Version 2.0.4 - December 12, 2011
|
||||
|
||||
* Fixed #47 - fix overriding properties
|
||||
* New option "position" to thumbnail and button helpers
|
||||
|
||||
|
||||
### Version 2.0.3 - November 29, 2011
|
||||
|
||||
* Fixed #29 - broken elastic transitions
|
||||
|
||||
|
||||
### Version 2.0.2 - November 28, 2011
|
||||
|
||||
* Fixed slideshow
|
||||
* Fixed scrollbars issue when displayed a very tall image
|
||||
* New option "nextClick" - navigate to next gallery item when user clicks the content
|
||||
* New option "modal" - to disable navigation and closing
|
||||
* Add 'metadata' plugin support
|
||||
* Add ability to create groups using 'data-fancybox-group' attribute
|
||||
* Updated manual usage to match earlier releases
|
||||
|
||||
|
||||
### Version 2.0.1 - November 23, 2011
|
||||
|
||||
* Fixed keyboard events inside form elements
|
||||
* Fixed manual usage
|
||||
|
||||
|
||||
### Version 2.0.0 - November 21, 2011
|
||||
|
||||
First release - completely rewritten, many new features and updated graphics.
|
||||
@@ -0,0 +1,217 @@
|
||||
fancyBox
|
||||
========
|
||||
|
||||
fancyBox is a tool that offers a nice and elegant way to add zooming functionality for images, html content and multi-media on your webpages.
|
||||
|
||||
More information and examples: http://www.fancyapps.com/fancybox/
|
||||
|
||||
License: http://www.fancyapps.com/fancybox/#license
|
||||
|
||||
Copyright (c) 2012 Janis Skarnelis - janis@fancyapps.com
|
||||
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
To get started, download the plugin, unzip it and copy files to your website/application directory.
|
||||
Load files in the <head> section of your HTML document. Make sure you also add the jQuery library.
|
||||
|
||||
<head>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="/fancybox/jquery.fancybox.css" type="text/css" media="screen" />
|
||||
<script type="text/javascript" src="/fancybox/jquery.fancybox.pack.js"></script>
|
||||
</head>
|
||||
|
||||
Create your links with a `title` if you want a title to be shown, and add a class:
|
||||
|
||||
<a href="large_image.jpg" class="fancybox" title="Sample title"><img src="small_image.jpg" /></a>
|
||||
|
||||
If you have a set of related items that you would like to group,
|
||||
additionally include a group name in the `rel` (or `data-fancybox-group`) attribute:
|
||||
|
||||
<a href="large_1.jpg" class="fancybox" rel="gallery" title="Sample title 1"><img src="small_1.jpg" /></a>
|
||||
<a href="large_2.jpg" class="fancybox" rel="gallery" title="Sample title 1"><img src="small_2.jpg" /></a>
|
||||
|
||||
Initialise the script like this:
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.fancybox').fancybox();
|
||||
});
|
||||
</script>
|
||||
|
||||
May also be passed an optional options object which will extend the default values. Example:
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.fancybox').fancybox({
|
||||
padding : 0,
|
||||
openEffect : 'elastic'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Tip: Automatically group and apply fancyBox to all images:
|
||||
|
||||
$("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.png'],a[href$='.gif']").attr('rel', 'gallery').fancybox();
|
||||
|
||||
Script uses the `href` attribute of the matched elements to obtain the location of the content and to figure out content type you want to display.
|
||||
You can specify type directly by adding classname (fancybox.image, fancybox.iframe, etc) or `data-fancybox-type` attribute:
|
||||
|
||||
//Ajax:
|
||||
<a href="/example.html" class="fancybox fancybox.ajax">Example</a>
|
||||
//or
|
||||
<a href="/example.html" class="fancybox" data-fancybox-type="ajax">Example</a>
|
||||
|
||||
//Iframe:
|
||||
<a href="example.html" class="fancybox fancybox.iframe">Example</a>
|
||||
|
||||
//Inline (will display an element with `id="example"`)
|
||||
<a href="#example" class="fancybox">Example</a>
|
||||
|
||||
//SWF:
|
||||
<a href="example.swf" class="fancybox">Example</a>
|
||||
|
||||
//Image:
|
||||
<a href="example.jpg" class="fancybox">Example</a>
|
||||
|
||||
Note, ajax requests are subject to the [same origin policy](http://en.wikipedia.org/wiki/Same_origin_policy).
|
||||
If fancyBox will not be able to get content type, it will try to guess based on 'href' and will quit silently if would not succeed.
|
||||
(this is different from previsous versions where 'ajax' was used as default type or an error message was displayed).
|
||||
|
||||
Advanced
|
||||
--------
|
||||
|
||||
### Helpers
|
||||
|
||||
Helpers provide a simple mechanism to extend the capabilities of fancyBox. There are two built-in helpers - 'overlay' and 'title'.
|
||||
You can disable them, set custom options or enable other helpers. Examples:
|
||||
|
||||
//Disable title helper
|
||||
$(".fancybox").fancybox({
|
||||
helpers: {
|
||||
title: null
|
||||
}
|
||||
});
|
||||
|
||||
//Disable overlay helper
|
||||
$(".fancybox").fancybox({
|
||||
helpers: {
|
||||
overlay : null
|
||||
}
|
||||
});
|
||||
|
||||
//Change title position and overlay color
|
||||
$(".fancybox").fancybox({
|
||||
helpers: {
|
||||
title : {
|
||||
type : 'inside'
|
||||
},
|
||||
overlay : {
|
||||
css : {
|
||||
'background' : 'rgba(255,255,255,0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Enable thumbnail helper and set custom options
|
||||
$(".fancybox").fancybox({
|
||||
helpers: {
|
||||
thumbs : {
|
||||
width: 50,
|
||||
height: 50
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
### API
|
||||
|
||||
Also available are event driven callback methods. The `this` keyword refers to the current or upcoming object (depends on callback method). Here is how you can change title:
|
||||
|
||||
$(".fancybox").fancybox({
|
||||
beforeLoad : function() {
|
||||
this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
|
||||
|
||||
/*
|
||||
"this.element" refers to current element, so you can, for example, use the "alt" attribute of the image to store the title:
|
||||
this.title = $(this.element).find('img').attr('alt');
|
||||
*/
|
||||
}
|
||||
});
|
||||
|
||||
It`s possible to open fancyBox programmatically in various ways:
|
||||
|
||||
//HTML content:
|
||||
$.fancybox( '<div><h1>Lorem Lipsum</h1><p>Lorem lipsum</p></div>', {
|
||||
title : 'Custom Title'
|
||||
});
|
||||
|
||||
//DOM element:
|
||||
$.fancybox( $("#inline"), {
|
||||
title : 'Custom Title'
|
||||
});
|
||||
|
||||
//Custom object:
|
||||
$.fancybox({
|
||||
href: 'example.jpg',
|
||||
title : 'Custom Title'
|
||||
});
|
||||
|
||||
//Array of objects:
|
||||
$.fancybox([
|
||||
{
|
||||
href: 'example1.jpg',
|
||||
title : 'Custom Title 1'
|
||||
},
|
||||
{
|
||||
href: 'example2.jpg',
|
||||
title : 'Custom Title 2'
|
||||
}
|
||||
], {
|
||||
padding: 0
|
||||
});
|
||||
|
||||
There are several methods that allow you to interact with and manipulate fancyBox, example:
|
||||
|
||||
//Close fancybox:
|
||||
$.fancybox.close();
|
||||
|
||||
There is a simply way to access wrapping elements using JS:
|
||||
|
||||
$.fancybox.wrap
|
||||
$.fancybox.skin
|
||||
$.fancybox.outer
|
||||
$.fancybox.inner
|
||||
|
||||
You can override CSS to customize the look. For example, make navigation arrows always visible,
|
||||
change width and move them outside of area (use this snippet after including fancybox.css):
|
||||
|
||||
.fancybox-nav span {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.fancybox-nav {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.fancybox-prev {
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.fancybox-next {
|
||||
right: -80px;
|
||||
}
|
||||
|
||||
In that case, you might want to increase space around box:
|
||||
|
||||
$(".fancybox").fancybox({
|
||||
margin : [20, 60, 20, 60]
|
||||
});
|
||||
|
||||
|
||||
Bug tracker
|
||||
-----------
|
||||
|
||||
Have a bug? Please create an issue on GitHub at https://github.com/fancyapps/fancyBox/issues
|
||||
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,15 @@
|
||||
<div style="max-width:700px;">
|
||||
<h2>Lorem ipsum dolor sit amet3</h2>
|
||||
<p>
|
||||
<a href="javascript:jQuery.fancybox.close();">Close me</a>
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas fermentum ante et sapien dignissim in viverra magna feugiat. Donec tempus ipsum nec neque dignissim quis eleifend eros gravida. Praesent nisi massa, sodales quis tincidunt ac, semper quis risus. In suscipit nisl sed leo aliquet consequat. Integer vitae augue in risus porttitor pellentesque eu eget odio. Fusce ut sagittis quam. Morbi aliquam interdum blandit. Integer pharetra tempor velit, aliquam dictum justo tempus sed. Morbi congue fringilla justo a feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent quis metus et nisl consectetur pharetra. Nam bibendum turpis eu metus luctus eu volutpat sem molestie. Nam sollicitudin porttitor lorem, ac ultricies est venenatis eu. Ut dignissim elit et orci feugiat ac placerat purus euismod. Ut mi lorem, cursus et sagittis elementum, luctus ac massa.
|
||||
</p>
|
||||
<p>
|
||||
Phasellus et ligula vel diam ullamcorper volutpat. Integer rhoncus rhoncus aliquam. Aliquam erat volutpat. Aenean luctus vestibulum placerat. Quisque quam neque, lacinia aliquet eleifend ac, aliquet blandit felis. Curabitur porta ultricies dui, sit amet mattis quam euismod a. Ut eleifend scelerisque neque, sit amet accumsan odio consequat ut. Proin facilisis auctor elit sed accumsan. Cras dapibus nisl in nisi rhoncus laoreet. Nullam pellentesque tortor libero, eget facilisis ipsum. Donec ultricies tellus tellus, in tincidunt purus. Nullam in est aliquam velit scelerisque blandit. In tincidunt, magna a dapibus imperdiet, quam urna elementum leo, vitae rhoncus nisl velit cursus velit. In dignissim sem ac mauris rhoncus ornare.
|
||||
</p>
|
||||
<p>
|
||||
Duis imperdiet velit vel quam malesuada suscipit imperdiet tellus hendrerit. Mauris vestibulum odio mauris, ut placerat leo. Mauris quis neque at tellus feugiat congue id non enim. Nam vehicula posuere nulla eget vehicula. Donec pretium purus nec ligula porta eu laoreet sapien venenatis. Nulla facilisi. Phasellus eget mi enim. Phasellus molestie tincidunt ultrices. Aenean id sem a tellus lobortis tincidunt. Nam laoreet nulla vel velit tincidunt ac rutrum libero malesuada. Nulla consequat dolor quis nisl tempor fermentum. Integer sodales pretium varius. Aenean a leo vitae odio dictum dignissim malesuada nec dolor. Phasellus adipiscing viverra est, ac sagittis libero sagittis quis. Sed interdum dapibus nunc et fringilla. Nunc vel velit et urna laoreet bibendum.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>fancyBox - iframe demo</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>fancyBox - iframe demo</h1>
|
||||
|
||||
<p>
|
||||
<a href="javascript:parent.jQuery.fancybox.close();">Close iframe parent</a>
|
||||
|
||||
|
|
||||
|
||||
<a href="javascript:parent.jQuery.fancybox.open({href : '1_b.jpg', title : 'My title'});">Change content</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam scelerisque justo ac eros consectetur bibendum. In hac habitasse platea dictumst. Nulla aliquam turpis et tellus elementum luctus. Duis sit amet rhoncus velit. Duis nisl ligula, mattis interdum blandit laoreet, mattis id ante. Cras pulvinar lacus vitae nisi egestas non euismod neque bibendum. Vestibulum faucibus libero id ante molestie ultricies. Vestibulum quis nibh felis. Vestibulum libero nisl, vehicula vel ullamcorper sit amet, tristique sit amet augue. Etiam urna neque, porttitor sed sodales lacinia, posuere a nisl. Vestibulum blandit neque in sapien volutpat ac condimentum sapien auctor. Ut imperdiet venenatis ultricies. Phasellus accumsan, sem eu placerat commodo, felis purus commodo ipsum, sit amet vulputate orci est viverra est.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Aenean velit est, condimentum ut iaculis ut, accumsan at mi. Maecenas velit mi, venenatis ut condimentum at, ultrices vel tortor. Curabitur pharetra ornare dapibus. Ut volutpat cursus semper. In hac habitasse platea dictumst. Donec eu iaculis ipsum. Morbi eu dolor velit, a semper nunc.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,312 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>fancyBox - Fancy jQuery Lightbox Alternative | Demonstration</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<!-- Add jQuery library -->
|
||||
<script type="text/javascript" src="../lib/jquery-1.10.1.min.js"></script>
|
||||
|
||||
<!-- Add mousewheel plugin (this is optional) -->
|
||||
<script type="text/javascript" src="../lib/jquery.mousewheel-3.0.6.pack.js"></script>
|
||||
|
||||
<!-- Add fancyBox main JS and CSS files -->
|
||||
<script type="text/javascript" src="../source/jquery.fancybox.js?v=2.1.5"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../source/jquery.fancybox.css?v=2.1.5" media="screen" />
|
||||
|
||||
<!-- Add Button helper (this is optional) -->
|
||||
<link rel="stylesheet" type="text/css" href="../source/helpers/jquery.fancybox-buttons.css?v=1.0.5" />
|
||||
<script type="text/javascript" src="../source/helpers/jquery.fancybox-buttons.js?v=1.0.5"></script>
|
||||
|
||||
<!-- Add Thumbnail helper (this is optional) -->
|
||||
<link rel="stylesheet" type="text/css" href="../source/helpers/jquery.fancybox-thumbs.css?v=1.0.7" />
|
||||
<script type="text/javascript" src="../source/helpers/jquery.fancybox-thumbs.js?v=1.0.7"></script>
|
||||
|
||||
<!-- Add Media helper (this is optional) -->
|
||||
<script type="text/javascript" src="../source/helpers/jquery.fancybox-media.js?v=1.0.6"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
/*
|
||||
* Simple image gallery. Uses default settings
|
||||
*/
|
||||
|
||||
$('.fancybox').fancybox();
|
||||
|
||||
/*
|
||||
* Different effects
|
||||
*/
|
||||
|
||||
// Change title type, overlay closing speed
|
||||
$(".fancybox-effects-a").fancybox({
|
||||
helpers: {
|
||||
title : {
|
||||
type : 'outside'
|
||||
},
|
||||
overlay : {
|
||||
speedOut : 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Disable opening and closing animations, change title type
|
||||
$(".fancybox-effects-b").fancybox({
|
||||
openEffect : 'none',
|
||||
closeEffect : 'none',
|
||||
|
||||
helpers : {
|
||||
title : {
|
||||
type : 'over'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set custom style, close if clicked, change title type and overlay color
|
||||
$(".fancybox-effects-c").fancybox({
|
||||
wrapCSS : 'fancybox-custom',
|
||||
closeClick : true,
|
||||
|
||||
openEffect : 'none',
|
||||
|
||||
helpers : {
|
||||
title : {
|
||||
type : 'inside'
|
||||
},
|
||||
overlay : {
|
||||
css : {
|
||||
'background' : 'rgba(238,238,238,0.85)'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Remove padding, set opening and closing animations, close if clicked and disable overlay
|
||||
$(".fancybox-effects-d").fancybox({
|
||||
padding: 0,
|
||||
|
||||
openEffect : 'elastic',
|
||||
openSpeed : 150,
|
||||
|
||||
closeEffect : 'elastic',
|
||||
closeSpeed : 150,
|
||||
|
||||
closeClick : true,
|
||||
|
||||
helpers : {
|
||||
overlay : null
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Button helper. Disable animations, hide close button, change title type and content
|
||||
*/
|
||||
|
||||
$('.fancybox-buttons').fancybox({
|
||||
openEffect : 'none',
|
||||
closeEffect : 'none',
|
||||
|
||||
prevEffect : 'none',
|
||||
nextEffect : 'none',
|
||||
|
||||
closeBtn : false,
|
||||
|
||||
helpers : {
|
||||
title : {
|
||||
type : 'inside'
|
||||
},
|
||||
buttons : {}
|
||||
},
|
||||
|
||||
afterLoad : function() {
|
||||
this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Thumbnail helper. Disable animations, hide close button, arrows and slide to next gallery item if clicked
|
||||
*/
|
||||
|
||||
$('.fancybox-thumbs').fancybox({
|
||||
prevEffect : 'none',
|
||||
nextEffect : 'none',
|
||||
|
||||
closeBtn : false,
|
||||
arrows : false,
|
||||
nextClick : true,
|
||||
|
||||
helpers : {
|
||||
thumbs : {
|
||||
width : 50,
|
||||
height : 50
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Media helper. Group items, disable animations, hide arrows, enable media and button helpers.
|
||||
*/
|
||||
$('.fancybox-media')
|
||||
.attr('rel', 'media-gallery')
|
||||
.fancybox({
|
||||
openEffect : 'none',
|
||||
closeEffect : 'none',
|
||||
prevEffect : 'none',
|
||||
nextEffect : 'none',
|
||||
|
||||
arrows : false,
|
||||
helpers : {
|
||||
media : {},
|
||||
buttons : {}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Open manually
|
||||
*/
|
||||
|
||||
$("#fancybox-manual-a").click(function() {
|
||||
$.fancybox.open('1_b.jpg');
|
||||
});
|
||||
|
||||
$("#fancybox-manual-b").click(function() {
|
||||
$.fancybox.open({
|
||||
href : 'iframe.html',
|
||||
type : 'iframe',
|
||||
padding : 5
|
||||
});
|
||||
});
|
||||
|
||||
$("#fancybox-manual-c").click(function() {
|
||||
$.fancybox.open([
|
||||
{
|
||||
href : '1_b.jpg',
|
||||
title : 'My title'
|
||||
}, {
|
||||
href : '2_b.jpg',
|
||||
title : '2nd title'
|
||||
}, {
|
||||
href : '3_b.jpg'
|
||||
}
|
||||
], {
|
||||
helpers : {
|
||||
thumbs : {
|
||||
width: 75,
|
||||
height: 50
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.fancybox-custom .fancybox-skin {
|
||||
box-shadow: 0 0 50px #222;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>fancyBox</h1>
|
||||
|
||||
<p>This is a demonstration. More information and examples: <a href="http://fancyapps.com/fancybox/">www.fancyapps.com/fancybox/</a></p>
|
||||
|
||||
<h3>Simple image gallery</h3>
|
||||
<p>
|
||||
<a class="fancybox" href="1_b.jpg" data-fancybox-group="gallery" title="Lorem ipsum dolor sit amet"><img src="1_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox" href="2_b.jpg" data-fancybox-group="gallery" title="Etiam quis mi eu elit temp"><img src="2_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox" href="3_b.jpg" data-fancybox-group="gallery" title="Cras neque mi, semper leon"><img src="3_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox" href="4_b.jpg" data-fancybox-group="gallery" title="Sed vel sapien vel sem uno"><img src="4_s.jpg" alt="" /></a>
|
||||
</p>
|
||||
|
||||
<h3>Different effects</h3>
|
||||
<p>
|
||||
<a class="fancybox-effects-a" href="5_b.jpg" title="Lorem ipsum dolor sit amet, consectetur adipiscing elit"><img src="5_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-effects-b" href="5_b.jpg" title="Lorem ipsum dolor sit amet, consectetur adipiscing elit"><img src="5_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-effects-c" href="5_b.jpg" title="Lorem ipsum dolor sit amet, consectetur adipiscing elit"><img src="5_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-effects-d" href="5_b.jpg" title="Lorem ipsum dolor sit amet, consectetur adipiscing elit"><img src="5_s.jpg" alt="" /></a>
|
||||
</p>
|
||||
|
||||
<h3>Various types</h3>
|
||||
<p>
|
||||
fancyBox will try to guess content type from href attribute but you can specify it directly by adding classname (fancybox.image, fancybox.iframe, etc).
|
||||
</p>
|
||||
<ul>
|
||||
<li><a class="fancybox" href="#inline1" title="Lorem ipsum dolor sit amet">Inline</a></li>
|
||||
<li><a class="fancybox fancybox.ajax" href="ajax.txt">Ajax</a></li>
|
||||
<li><a class="fancybox fancybox.iframe" href="iframe.html">Iframe</a></li>
|
||||
<li><a class="fancybox" href="http://www.adobe.com/jp/events/cs3_web_edition_tour/swfs/perform.swf">Swf</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="inline1" style="width:400px;display: none;">
|
||||
<h3>Etiam quis mi eu elit</h3>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam quis mi eu elit tempor facilisis id et neque. Nulla sit amet sem sapien. Vestibulum imperdiet porta ante ac ornare. Nulla et lorem eu nibh adipiscing ultricies nec at lacus. Cras laoreet ultricies sem, at blandit mi eleifend aliquam. Nunc enim ipsum, vehicula non pretium varius, cursus ac tortor. Vivamus fringilla congue laoreet. Quisque ultrices sodales orci, quis rhoncus justo auctor in. Phasellus dui eros, bibendum eu feugiat ornare, faucibus eu mi. Nunc aliquet tempus sem, id aliquam diam varius ac. Maecenas nisl nunc, molestie vitae eleifend vel, iaculis sed magna. Aenean tempus lacus vitae orci posuere porttitor eget non felis. Donec lectus elit, aliquam nec eleifend sit amet, vestibulum sed nunc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Ajax example will not run from your local computer and requires a server to run.
|
||||
</p>
|
||||
|
||||
<h3>Button helper</h3>
|
||||
<p>
|
||||
<a class="fancybox-buttons" data-fancybox-group="button" href="1_b.jpg"><img src="1_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-buttons" data-fancybox-group="button" href="2_b.jpg"><img src="2_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-buttons" data-fancybox-group="button" href="3_b.jpg"><img src="3_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-buttons" data-fancybox-group="button" href="4_b.jpg"><img src="4_s.jpg" alt="" /></a>
|
||||
</p>
|
||||
|
||||
<h3>Thumbnail helper</h3>
|
||||
<p>
|
||||
<a class="fancybox-thumbs" data-fancybox-group="thumb" href="4_b.jpg"><img src="4_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-thumbs" data-fancybox-group="thumb" href="3_b.jpg"><img src="3_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-thumbs" data-fancybox-group="thumb" href="2_b.jpg"><img src="2_s.jpg" alt="" /></a>
|
||||
|
||||
<a class="fancybox-thumbs" data-fancybox-group="thumb" href="1_b.jpg"><img src="1_s.jpg" alt="" /></a>
|
||||
</p>
|
||||
|
||||
<h3>Media helper</h3>
|
||||
<p>
|
||||
Will not run from your local computer, requires a server to run.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a class="fancybox-media" href="http://www.youtube.com/watch?v=opj24KnzrWo">Youtube</a></li>
|
||||
<li><a class="fancybox-media" href="http://vimeo.com/47480346">Vimeo</a></li>
|
||||
<li><a class="fancybox-media" href="http://www.metacafe.com/watch/7635964/">Metacafe</a></li>
|
||||
<li><a class="fancybox-media" href="http://www.dailymotion.com/video/xoeylt_electric-guest-this-head-i-hold_music">Dailymotion</a></li>
|
||||
<li><a class="fancybox-media" href="http://twitvid.com/QY7MD">Twitvid</a></li>
|
||||
<li><a class="fancybox-media" href="http://twitpic.com/7p93st">Twitpic</a></li>
|
||||
<li><a class="fancybox-media" href="http://instagr.am/p/IejkuUGxQn">Instagram</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Open manually</h3>
|
||||
<ul>
|
||||
<li><a id="fancybox-manual-a" href="javascript:;">Open single item</a></li>
|
||||
<li><a id="fancybox-manual-b" href="javascript:;">Open single item, custom options</a></li>
|
||||
<li><a id="fancybox-manual-c" href="javascript:;">Open gallery</a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Photo Credit: Instagrammer @whitjohns
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
|
||||
* Licensed under the MIT License (LICENSE.txt).
|
||||
*
|
||||
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
|
||||
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
|
||||
* Thanks to: Seamus Leahy for adding deltaX and deltaY
|
||||
*
|
||||
* Version: 3.0.6
|
||||
*
|
||||
* Requires: 1.2.2+
|
||||
*/
|
||||
(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;b.axis!==void 0&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);b.wheelDeltaY!==void 0&&(g=b.wheelDeltaY/120);b.wheelDeltaX!==void 0&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=
|
||||
d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,false);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
|
||||
|
After Width: | Height: | Size: 43 B |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1003 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,97 @@
|
||||
#fancybox-buttons {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 8050;
|
||||
}
|
||||
|
||||
#fancybox-buttons.top {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
#fancybox-buttons.bottom {
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#fancybox-buttons ul {
|
||||
display: block;
|
||||
width: 166px;
|
||||
height: 30px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
border: 1px solid #111;
|
||||
border-radius: 3px;
|
||||
-webkit-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
|
||||
-moz-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
|
||||
background: rgb(50,50,50);
|
||||
background: -moz-linear-gradient(top, rgb(68,68,68) 0%, rgb(52,52,52) 50%, rgb(41,41,41) 50%, rgb(51,51,51) 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(68,68,68)), color-stop(50%,rgb(52,52,52)), color-stop(50%,rgb(41,41,41)), color-stop(100%,rgb(51,51,51)));
|
||||
background: -webkit-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%);
|
||||
background: -o-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%);
|
||||
background: -ms-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%);
|
||||
background: linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#444444', endColorstr='#222222',GradientType=0 );
|
||||
}
|
||||
|
||||
#fancybox-buttons ul li {
|
||||
float: left;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#fancybox-buttons a {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-indent: -9999px;
|
||||
background-color: transparent;
|
||||
background-image: url('fancybox_buttons.png');
|
||||
background-repeat: no-repeat;
|
||||
outline: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#fancybox-buttons a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnPrev {
|
||||
background-position: 5px 0;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnNext {
|
||||
background-position: -33px 0;
|
||||
border-right: 1px solid #3e3e3e;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnPlay {
|
||||
background-position: 0 -30px;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnPlayOn {
|
||||
background-position: -30px -30px;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnToggle {
|
||||
background-position: 3px -60px;
|
||||
border-left: 1px solid #111;
|
||||
border-right: 1px solid #3e3e3e;
|
||||
width: 35px
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnToggleOn {
|
||||
background-position: -27px -60px;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnClose {
|
||||
border-left: 1px solid #111;
|
||||
width: 35px;
|
||||
background-position: -56px 0px;
|
||||
}
|
||||
|
||||
#fancybox-buttons a.btnDisabled {
|
||||
opacity : 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*!
|
||||
* Buttons helper for fancyBox
|
||||
* version: 1.0.5 (Mon, 15 Oct 2012)
|
||||
* @requires fancyBox v2.0 or later
|
||||
*
|
||||
* Usage:
|
||||
* $(".fancybox").fancybox({
|
||||
* helpers : {
|
||||
* buttons: {
|
||||
* position : 'top'
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
*/
|
||||
(function ($) {
|
||||
//Shortcut for fancyBox object
|
||||
var F = $.fancybox;
|
||||
|
||||
//Add helper object
|
||||
F.helpers.buttons = {
|
||||
defaults : {
|
||||
skipSingle : false, // disables if gallery contains single image
|
||||
position : 'top', // 'top' or 'bottom'
|
||||
tpl : '<div id="fancybox-buttons"><ul><li><a class="btnPrev" title="Previous" href="javascript:;"></a></li><li><a class="btnPlay" title="Start slideshow" href="javascript:;"></a></li><li><a class="btnNext" title="Next" href="javascript:;"></a></li><li><a class="btnToggle" title="Toggle size" href="javascript:;"></a></li><li><a class="btnClose" title="Close" href="javascript:;"></a></li></ul></div>'
|
||||
},
|
||||
|
||||
list : null,
|
||||
buttons: null,
|
||||
|
||||
beforeLoad: function (opts, obj) {
|
||||
//Remove self if gallery do not have at least two items
|
||||
|
||||
if (opts.skipSingle && obj.group.length < 2) {
|
||||
obj.helpers.buttons = false;
|
||||
obj.closeBtn = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Increase top margin to give space for buttons
|
||||
obj.margin[ opts.position === 'bottom' ? 2 : 0 ] += 30;
|
||||
},
|
||||
|
||||
onPlayStart: function () {
|
||||
if (this.buttons) {
|
||||
this.buttons.play.attr('title', 'Pause slideshow').addClass('btnPlayOn');
|
||||
}
|
||||
},
|
||||
|
||||
onPlayEnd: function () {
|
||||
if (this.buttons) {
|
||||
this.buttons.play.attr('title', 'Start slideshow').removeClass('btnPlayOn');
|
||||
}
|
||||
},
|
||||
|
||||
afterShow: function (opts, obj) {
|
||||
var buttons = this.buttons;
|
||||
|
||||
if (!buttons) {
|
||||
this.list = $(opts.tpl).addClass(opts.position).appendTo('body');
|
||||
|
||||
buttons = {
|
||||
prev : this.list.find('.btnPrev').click( F.prev ),
|
||||
next : this.list.find('.btnNext').click( F.next ),
|
||||
play : this.list.find('.btnPlay').click( F.play ),
|
||||
toggle : this.list.find('.btnToggle').click( F.toggle ),
|
||||
close : this.list.find('.btnClose').click( F.close )
|
||||
}
|
||||
}
|
||||
|
||||
//Prev
|
||||
if (obj.index > 0 || obj.loop) {
|
||||
buttons.prev.removeClass('btnDisabled');
|
||||
} else {
|
||||
buttons.prev.addClass('btnDisabled');
|
||||
}
|
||||
|
||||
//Next / Play
|
||||
if (obj.loop || obj.index < obj.group.length - 1) {
|
||||
buttons.next.removeClass('btnDisabled');
|
||||
buttons.play.removeClass('btnDisabled');
|
||||
|
||||
} else {
|
||||
buttons.next.addClass('btnDisabled');
|
||||
buttons.play.addClass('btnDisabled');
|
||||
}
|
||||
|
||||
this.buttons = buttons;
|
||||
|
||||
this.onUpdate(opts, obj);
|
||||
},
|
||||
|
||||
onUpdate: function (opts, obj) {
|
||||
var toggle;
|
||||
|
||||
if (!this.buttons) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggle = this.buttons.toggle.removeClass('btnDisabled btnToggleOn');
|
||||
|
||||
//Size toggle button
|
||||
if (obj.canShrink) {
|
||||
toggle.addClass('btnToggleOn');
|
||||
|
||||
} else if (!obj.canExpand) {
|
||||
toggle.addClass('btnDisabled');
|
||||
}
|
||||
},
|
||||
|
||||
beforeClose: function () {
|
||||
if (this.list) {
|
||||
this.list.remove();
|
||||
}
|
||||
|
||||
this.list = null;
|
||||
this.buttons = null;
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,199 @@
|
||||
/*!
|
||||
* Media helper for fancyBox
|
||||
* version: 1.0.6 (Fri, 14 Jun 2013)
|
||||
* @requires fancyBox v2.0 or later
|
||||
*
|
||||
* Usage:
|
||||
* $(".fancybox").fancybox({
|
||||
* helpers : {
|
||||
* media: true
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Set custom URL parameters:
|
||||
* $(".fancybox").fancybox({
|
||||
* helpers : {
|
||||
* media: {
|
||||
* youtube : {
|
||||
* params : {
|
||||
* autoplay : 0
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Or:
|
||||
* $(".fancybox").fancybox({,
|
||||
* helpers : {
|
||||
* media: true
|
||||
* },
|
||||
* youtube : {
|
||||
* autoplay: 0
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Supports:
|
||||
*
|
||||
* Youtube
|
||||
* http://www.youtube.com/watch?v=opj24KnzrWo
|
||||
* http://www.youtube.com/embed/opj24KnzrWo
|
||||
* http://youtu.be/opj24KnzrWo
|
||||
* http://www.youtube-nocookie.com/embed/opj24KnzrWo
|
||||
* Vimeo
|
||||
* http://vimeo.com/40648169
|
||||
* http://vimeo.com/channels/staffpicks/38843628
|
||||
* http://vimeo.com/groups/surrealism/videos/36516384
|
||||
* http://player.vimeo.com/video/45074303
|
||||
* Metacafe
|
||||
* http://www.metacafe.com/watch/7635964/dr_seuss_the_lorax_movie_trailer/
|
||||
* http://www.metacafe.com/watch/7635964/
|
||||
* Dailymotion
|
||||
* http://www.dailymotion.com/video/xoytqh_dr-seuss-the-lorax-premiere_people
|
||||
* Twitvid
|
||||
* http://twitvid.com/QY7MD
|
||||
* Twitpic
|
||||
* http://twitpic.com/7p93st
|
||||
* Instagram
|
||||
* http://instagr.am/p/IejkuUGxQn/
|
||||
* http://instagram.com/p/IejkuUGxQn/
|
||||
* Google maps
|
||||
* http://maps.google.com/maps?q=Eiffel+Tower,+Avenue+Gustave+Eiffel,+Paris,+France&t=h&z=17
|
||||
* http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16
|
||||
* http://maps.google.com/?ll=48.859463,2.292626&spn=0.000965,0.002642&t=m&z=19&layer=c&cbll=48.859524,2.292532&panoid=YJ0lq28OOy3VT2IqIuVY0g&cbp=12,151.58,,0,-15.56
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
//Shortcut for fancyBox object
|
||||
var F = $.fancybox,
|
||||
format = function( url, rez, params ) {
|
||||
params = params || '';
|
||||
|
||||
if ( $.type( params ) === "object" ) {
|
||||
params = $.param(params, true);
|
||||
}
|
||||
|
||||
$.each(rez, function(key, value) {
|
||||
url = url.replace( '$' + key, value || '' );
|
||||
});
|
||||
|
||||
if (params.length) {
|
||||
url += ( url.indexOf('?') > 0 ? '&' : '?' ) + params;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
//Add helper object
|
||||
F.helpers.media = {
|
||||
defaults : {
|
||||
youtube : {
|
||||
matcher : /(youtube\.com|youtu\.be|youtube-nocookie\.com)\/(watch\?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*)).*/i,
|
||||
params : {
|
||||
autoplay : 1,
|
||||
autohide : 1,
|
||||
fs : 1,
|
||||
rel : 0,
|
||||
hd : 1,
|
||||
wmode : 'opaque',
|
||||
enablejsapi : 1
|
||||
},
|
||||
type : 'iframe',
|
||||
url : '//www.youtube.com/embed/$3'
|
||||
},
|
||||
vimeo : {
|
||||
matcher : /(?:vimeo(?:pro)?.com)\/(?:[^\d]+)?(\d+)(?:.*)/,
|
||||
params : {
|
||||
autoplay : 1,
|
||||
hd : 1,
|
||||
show_title : 1,
|
||||
show_byline : 1,
|
||||
show_portrait : 0,
|
||||
fullscreen : 1
|
||||
},
|
||||
type : 'iframe',
|
||||
url : '//player.vimeo.com/video/$1'
|
||||
},
|
||||
metacafe : {
|
||||
matcher : /metacafe.com\/(?:watch|fplayer)\/([\w\-]{1,10})/,
|
||||
params : {
|
||||
autoPlay : 'yes'
|
||||
},
|
||||
type : 'swf',
|
||||
url : function( rez, params, obj ) {
|
||||
obj.swf.flashVars = 'playerVars=' + $.param( params, true );
|
||||
|
||||
return '//www.metacafe.com/fplayer/' + rez[1] + '/.swf';
|
||||
}
|
||||
},
|
||||
dailymotion : {
|
||||
matcher : /dailymotion.com\/video\/(.*)\/?(.*)/,
|
||||
params : {
|
||||
additionalInfos : 0,
|
||||
autoStart : 1
|
||||
},
|
||||
type : 'swf',
|
||||
url : '//www.dailymotion.com/swf/video/$1'
|
||||
},
|
||||
twitvid : {
|
||||
matcher : /twitvid\.com\/([a-zA-Z0-9_\-\?\=]+)/i,
|
||||
params : {
|
||||
autoplay : 0
|
||||
},
|
||||
type : 'iframe',
|
||||
url : '//www.twitvid.com/embed.php?guid=$1'
|
||||
},
|
||||
twitpic : {
|
||||
matcher : /twitpic\.com\/(?!(?:place|photos|events)\/)([a-zA-Z0-9\?\=\-]+)/i,
|
||||
type : 'image',
|
||||
url : '//twitpic.com/show/full/$1/'
|
||||
},
|
||||
instagram : {
|
||||
matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,
|
||||
type : 'image',
|
||||
url : '//$1/p/$2/media/?size=l'
|
||||
},
|
||||
google_maps : {
|
||||
matcher : /maps\.google\.([a-z]{2,3}(\.[a-z]{2})?)\/(\?ll=|maps\?)(.*)/i,
|
||||
type : 'iframe',
|
||||
url : function( rez ) {
|
||||
return '//maps.google.' + rez[1] + '/' + rez[3] + '' + rez[4] + '&output=' + (rez[4].indexOf('layer=c') > 0 ? 'svembed' : 'embed');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeLoad : function(opts, obj) {
|
||||
var url = obj.href || '',
|
||||
type = false,
|
||||
what,
|
||||
item,
|
||||
rez,
|
||||
params;
|
||||
|
||||
for (what in opts) {
|
||||
if (opts.hasOwnProperty(what)) {
|
||||
item = opts[ what ];
|
||||
rez = url.match( item.matcher );
|
||||
|
||||
if (rez) {
|
||||
type = item.type;
|
||||
params = $.extend(true, {}, item.params, obj[ what ] || ($.isPlainObject(opts[ what ]) ? opts[ what ].params : null));
|
||||
|
||||
url = $.type( item.url ) === "function" ? item.url.call( this, rez, params, obj ) : format( item.url, rez, params );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type) {
|
||||
obj.href = url;
|
||||
obj.type = type;
|
||||
|
||||
obj.autoHeight = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,55 @@
|
||||
#fancybox-thumbs {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 8050;
|
||||
}
|
||||
|
||||
#fancybox-thumbs.bottom {
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
#fancybox-thumbs.top {
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul li {
|
||||
float: left;
|
||||
padding: 1px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul li.active {
|
||||
opacity: 0.75;
|
||||
padding: 0;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul li:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul li a {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #222;
|
||||
background: #111;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#fancybox-thumbs ul li img {
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
max-width: none;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*!
|
||||
* Thumbnail helper for fancyBox
|
||||
* version: 1.0.7 (Mon, 01 Oct 2012)
|
||||
* @requires fancyBox v2.0 or later
|
||||
*
|
||||
* Usage:
|
||||
* $(".fancybox").fancybox({
|
||||
* helpers : {
|
||||
* thumbs: {
|
||||
* width : 50,
|
||||
* height : 50
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
*/
|
||||
(function ($) {
|
||||
//Shortcut for fancyBox object
|
||||
var F = $.fancybox;
|
||||
|
||||
//Add helper object
|
||||
F.helpers.thumbs = {
|
||||
defaults : {
|
||||
width : 50, // thumbnail width
|
||||
height : 50, // thumbnail height
|
||||
position : 'bottom', // 'top' or 'bottom'
|
||||
source : function ( item ) { // function to obtain the URL of the thumbnail image
|
||||
var href;
|
||||
|
||||
if (item.element) {
|
||||
href = $(item.element).find('img').attr('src');
|
||||
}
|
||||
|
||||
if (!href && item.type === 'image' && item.href) {
|
||||
href = item.href;
|
||||
}
|
||||
|
||||
return href;
|
||||
}
|
||||
},
|
||||
|
||||
wrap : null,
|
||||
list : null,
|
||||
width : 0,
|
||||
|
||||
init: function (opts, obj) {
|
||||
var that = this,
|
||||
list,
|
||||
thumbWidth = opts.width,
|
||||
thumbHeight = opts.height,
|
||||
thumbSource = opts.source;
|
||||
|
||||
//Build list structure
|
||||
list = '';
|
||||
|
||||
for (var n = 0; n < obj.group.length; n++) {
|
||||
list += '<li><a style="width:' + thumbWidth + 'px;height:' + thumbHeight + 'px;" href="javascript:jQuery.fancybox.jumpto(' + n + ');"></a></li>';
|
||||
}
|
||||
|
||||
this.wrap = $('<div id="fancybox-thumbs"></div>').addClass(opts.position).appendTo('body');
|
||||
this.list = $('<ul>' + list + '</ul>').appendTo(this.wrap);
|
||||
|
||||
//Load each thumbnail
|
||||
$.each(obj.group, function (i) {
|
||||
var href = thumbSource( obj.group[ i ] );
|
||||
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("<img />").load(function () {
|
||||
var width = this.width,
|
||||
height = this.height,
|
||||
widthRatio, heightRatio, parent;
|
||||
|
||||
if (!that.list || !width || !height) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Calculate thumbnail width/height and center it
|
||||
widthRatio = width / thumbWidth;
|
||||
heightRatio = height / thumbHeight;
|
||||
|
||||
parent = that.list.children().eq(i).find('a');
|
||||
|
||||
if (widthRatio >= 1 && heightRatio >= 1) {
|
||||
if (widthRatio > heightRatio) {
|
||||
width = Math.floor(width / heightRatio);
|
||||
height = thumbHeight;
|
||||
|
||||
} else {
|
||||
width = thumbWidth;
|
||||
height = Math.floor(height / widthRatio);
|
||||
}
|
||||
}
|
||||
|
||||
$(this).css({
|
||||
width : width,
|
||||
height : height,
|
||||
top : Math.floor(thumbHeight / 2 - height / 2),
|
||||
left : Math.floor(thumbWidth / 2 - width / 2)
|
||||
});
|
||||
|
||||
parent.width(thumbWidth).height(thumbHeight);
|
||||
|
||||
$(this).hide().appendTo(parent).fadeIn(300);
|
||||
|
||||
}).attr('src', href);
|
||||
});
|
||||
|
||||
//Set initial width
|
||||
this.width = this.list.children().eq(0).outerWidth(true);
|
||||
|
||||
this.list.width(this.width * (obj.group.length + 1)).css('left', Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5)));
|
||||
},
|
||||
|
||||
beforeLoad: function (opts, obj) {
|
||||
//Remove self if gallery do not have at least two items
|
||||
if (obj.group.length < 2) {
|
||||
obj.helpers.thumbs = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Increase bottom margin to give space for thumbs
|
||||
obj.margin[ opts.position === 'top' ? 0 : 2 ] += ((opts.height) + 15);
|
||||
},
|
||||
|
||||
afterShow: function (opts, obj) {
|
||||
//Check if exists and create or update list
|
||||
if (this.list) {
|
||||
this.onUpdate(opts, obj);
|
||||
|
||||
} else {
|
||||
this.init(opts, obj);
|
||||
}
|
||||
|
||||
//Set active element
|
||||
this.list.children().removeClass('active').eq(obj.index).addClass('active');
|
||||
},
|
||||
|
||||
//Center list
|
||||
onUpdate: function (opts, obj) {
|
||||
if (this.list) {
|
||||
this.list.stop(true).animate({
|
||||
'left': Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5))
|
||||
}, 150);
|
||||
}
|
||||
},
|
||||
|
||||
beforeClose: function () {
|
||||
if (this.wrap) {
|
||||
this.wrap.remove();
|
||||
}
|
||||
|
||||
this.wrap = null;
|
||||
this.list = null;
|
||||
this.width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,274 @@
|
||||
/*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */
|
||||
.fancybox-wrap,
|
||||
.fancybox-skin,
|
||||
.fancybox-outer,
|
||||
.fancybox-inner,
|
||||
.fancybox-image,
|
||||
.fancybox-wrap iframe,
|
||||
.fancybox-wrap object,
|
||||
.fancybox-nav,
|
||||
.fancybox-nav span,
|
||||
.fancybox-tmp
|
||||
{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.fancybox-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 8020;
|
||||
}
|
||||
|
||||
.fancybox-skin {
|
||||
position: relative;
|
||||
background: #f9f9f9;
|
||||
color: #444;
|
||||
text-shadow: none;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fancybox-opened {
|
||||
z-index: 8030;
|
||||
}
|
||||
|
||||
.fancybox-opened .fancybox-skin {
|
||||
-webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
||||
-moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.fancybox-outer, .fancybox-inner {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fancybox-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fancybox-type-iframe .fancybox-inner {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.fancybox-error {
|
||||
color: #444;
|
||||
font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
margin: 0;
|
||||
padding: 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fancybox-image, .fancybox-iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fancybox-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span {
|
||||
background-image: url('fancybox_sprite.png');
|
||||
}
|
||||
|
||||
#fancybox-loading {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -22px;
|
||||
margin-left: -22px;
|
||||
background-position: 0 -108px;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
z-index: 8060;
|
||||
}
|
||||
|
||||
#fancybox-loading div {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: url('fancybox_loading.gif') center center no-repeat;
|
||||
}
|
||||
|
||||
.fancybox-close {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
right: -18px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
z-index: 8040;
|
||||
}
|
||||
|
||||
.fancybox-nav {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
background: transparent url('blank.gif'); /* helps IE */
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
z-index: 8040;
|
||||
}
|
||||
|
||||
.fancybox-prev {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.fancybox-next {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.fancybox-nav span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 36px;
|
||||
height: 34px;
|
||||
margin-top: -18px;
|
||||
cursor: pointer;
|
||||
z-index: 8040;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.fancybox-prev span {
|
||||
left: 10px;
|
||||
background-position: 0 -36px;
|
||||
}
|
||||
|
||||
.fancybox-next span {
|
||||
right: 10px;
|
||||
background-position: 0 -72px;
|
||||
}
|
||||
|
||||
.fancybox-nav:hover span {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.fancybox-tmp {
|
||||
position: absolute;
|
||||
top: -99999px;
|
||||
left: -99999px;
|
||||
visibility: hidden;
|
||||
max-width: 99999px;
|
||||
max-height: 99999px;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Overlay helper */
|
||||
|
||||
.fancybox-lock {
|
||||
overflow: hidden !important;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fancybox-lock body {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.fancybox-lock-test {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.fancybox-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
z-index: 8010;
|
||||
background: url('fancybox_overlay.png');
|
||||
}
|
||||
|
||||
.fancybox-overlay-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.fancybox-lock .fancybox-overlay {
|
||||
overflow: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* Title helper */
|
||||
|
||||
.fancybox-title {
|
||||
visibility: hidden;
|
||||
font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
position: relative;
|
||||
text-shadow: none;
|
||||
z-index: 8050;
|
||||
}
|
||||
|
||||
.fancybox-opened .fancybox-title {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.fancybox-title-float-wrap {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 50%;
|
||||
margin-bottom: -35px;
|
||||
z-index: 8050;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fancybox-title-float-wrap .child {
|
||||
display: inline-block;
|
||||
margin-right: -100%;
|
||||
padding: 2px 20px;
|
||||
background: transparent; /* Fallback for web browsers that doesn't support RGBa */
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
-webkit-border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
text-shadow: 0 1px 2px #222;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fancybox-title-outside-wrap {
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fancybox-title-inside-wrap {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.fancybox-title-over-wrap {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
background: #000;
|
||||
background: rgba(0, 0, 0, .8);
|
||||
}
|
||||
|
||||
/*Retina graphics!*/
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
|
||||
only screen and (min--moz-device-pixel-ratio: 1.5),
|
||||
only screen and (min-device-pixel-ratio: 1.5){
|
||||
|
||||
#fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span {
|
||||
background-image: url('fancybox_sprite@2x.png');
|
||||
background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/
|
||||
}
|
||||
|
||||
#fancybox-loading div {
|
||||
background-image: url('fancybox_loading@2x.gif');
|
||||
background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */
|
||||
(function(r,G,f,v){var J=f("html"),n=f(r),p=f(G),b=f.fancybox=function(){b.open.apply(this,arguments)},I=navigator.userAgent.match(/msie/i),B=null,s=G.createTouch!==v,t=function(a){return a&&a.hasOwnProperty&&a instanceof f},q=function(a){return a&&"string"===f.type(a)},E=function(a){return q(a)&&0<a.indexOf("%")},l=function(a,d){var e=parseInt(a,10)||0;d&&E(a)&&(e*=b.getViewport()[d]/100);return Math.ceil(e)},w=function(a,b){return l(a,b)+"px"};f.extend(b,{version:"2.1.5",defaults:{padding:15,margin:20,
|
||||
width:800,height:600,minWidth:100,minHeight:100,maxWidth:9999,maxHeight:9999,pixelRatio:1,autoSize:!0,autoHeight:!1,autoWidth:!1,autoResize:!0,autoCenter:!s,fitToView:!0,aspectRatio:!1,topRatio:0.5,leftRatio:0.5,scrolling:"auto",wrapCSS:"",arrows:!0,closeBtn:!0,closeClick:!1,nextClick:!1,mouseWheel:!0,autoPlay:!1,playSpeed:3E3,preload:3,modal:!1,loop:!0,ajax:{dataType:"html",headers:{"X-fancyBox":!0}},iframe:{scrolling:"auto",preload:!0},swf:{wmode:"transparent",allowfullscreen:"true",allowscriptaccess:"always"},
|
||||
keys:{next:{13:"left",34:"up",39:"left",40:"up"},prev:{8:"right",33:"down",37:"right",38:"down"},close:[27],play:[32],toggle:[70]},direction:{next:"left",prev:"right"},scrollOutside:!0,index:0,type:null,href:null,content:null,title:null,tpl:{wrap:'<div class="fancybox-wrap" tabIndex="-1"><div class="fancybox-skin"><div class="fancybox-outer"><div class="fancybox-inner"></div></div></div></div>',image:'<img class="fancybox-image" src="{href}" alt="" />',iframe:'<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen'+
|
||||
(I?' allowtransparency="true"':"")+"></iframe>",error:'<p class="fancybox-error">The requested content cannot be loaded.<br/>Please try again later.</p>',closeBtn:'<a title="Close" class="fancybox-item fancybox-close" href="javascript:;"></a>',next:'<a title="Next" class="fancybox-nav fancybox-next" href="javascript:;"><span></span></a>',prev:'<a title="Previous" class="fancybox-nav fancybox-prev" href="javascript:;"><span></span></a>'},openEffect:"fade",openSpeed:250,openEasing:"swing",openOpacity:!0,
|
||||
openMethod:"zoomIn",closeEffect:"fade",closeSpeed:250,closeEasing:"swing",closeOpacity:!0,closeMethod:"zoomOut",nextEffect:"elastic",nextSpeed:250,nextEasing:"swing",nextMethod:"changeIn",prevEffect:"elastic",prevSpeed:250,prevEasing:"swing",prevMethod:"changeOut",helpers:{overlay:!0,title:!0},onCancel:f.noop,beforeLoad:f.noop,afterLoad:f.noop,beforeShow:f.noop,afterShow:f.noop,beforeChange:f.noop,beforeClose:f.noop,afterClose:f.noop},group:{},opts:{},previous:null,coming:null,current:null,isActive:!1,
|
||||
isOpen:!1,isOpened:!1,wrap:null,skin:null,outer:null,inner:null,player:{timer:null,isActive:!1},ajaxLoad:null,imgPreload:null,transitions:{},helpers:{},open:function(a,d){if(a&&(f.isPlainObject(d)||(d={}),!1!==b.close(!0)))return f.isArray(a)||(a=t(a)?f(a).get():[a]),f.each(a,function(e,c){var k={},g,h,j,m,l;"object"===f.type(c)&&(c.nodeType&&(c=f(c)),t(c)?(k={href:c.data("fancybox-href")||c.attr("href"),title:c.data("fancybox-title")||c.attr("title"),isDom:!0,element:c},f.metadata&&f.extend(!0,k,
|
||||
c.metadata())):k=c);g=d.href||k.href||(q(c)?c:null);h=d.title!==v?d.title:k.title||"";m=(j=d.content||k.content)?"html":d.type||k.type;!m&&k.isDom&&(m=c.data("fancybox-type"),m||(m=(m=c.prop("class").match(/fancybox\.(\w+)/))?m[1]:null));q(g)&&(m||(b.isImage(g)?m="image":b.isSWF(g)?m="swf":"#"===g.charAt(0)?m="inline":q(c)&&(m="html",j=c)),"ajax"===m&&(l=g.split(/\s+/,2),g=l.shift(),l=l.shift()));j||("inline"===m?g?j=f(q(g)?g.replace(/.*(?=#[^\s]+$)/,""):g):k.isDom&&(j=c):"html"===m?j=g:!m&&(!g&&
|
||||
k.isDom)&&(m="inline",j=c));f.extend(k,{href:g,type:m,content:j,title:h,selector:l});a[e]=k}),b.opts=f.extend(!0,{},b.defaults,d),d.keys!==v&&(b.opts.keys=d.keys?f.extend({},b.defaults.keys,d.keys):!1),b.group=a,b._start(b.opts.index)},cancel:function(){var a=b.coming;a&&!1!==b.trigger("onCancel")&&(b.hideLoading(),b.ajaxLoad&&b.ajaxLoad.abort(),b.ajaxLoad=null,b.imgPreload&&(b.imgPreload.onload=b.imgPreload.onerror=null),a.wrap&&a.wrap.stop(!0,!0).trigger("onReset").remove(),b.coming=null,b.current||
|
||||
b._afterZoomOut(a))},close:function(a){b.cancel();!1!==b.trigger("beforeClose")&&(b.unbindEvents(),b.isActive&&(!b.isOpen||!0===a?(f(".fancybox-wrap").stop(!0).trigger("onReset").remove(),b._afterZoomOut()):(b.isOpen=b.isOpened=!1,b.isClosing=!0,f(".fancybox-item, .fancybox-nav").remove(),b.wrap.stop(!0,!0).removeClass("fancybox-opened"),b.transitions[b.current.closeMethod]())))},play:function(a){var d=function(){clearTimeout(b.player.timer)},e=function(){d();b.current&&b.player.isActive&&(b.player.timer=
|
||||
setTimeout(b.next,b.current.playSpeed))},c=function(){d();p.unbind(".player");b.player.isActive=!1;b.trigger("onPlayEnd")};if(!0===a||!b.player.isActive&&!1!==a){if(b.current&&(b.current.loop||b.current.index<b.group.length-1))b.player.isActive=!0,p.bind({"onCancel.player beforeClose.player":c,"onUpdate.player":e,"beforeLoad.player":d}),e(),b.trigger("onPlayStart")}else c()},next:function(a){var d=b.current;d&&(q(a)||(a=d.direction.next),b.jumpto(d.index+1,a,"next"))},prev:function(a){var d=b.current;
|
||||
d&&(q(a)||(a=d.direction.prev),b.jumpto(d.index-1,a,"prev"))},jumpto:function(a,d,e){var c=b.current;c&&(a=l(a),b.direction=d||c.direction[a>=c.index?"next":"prev"],b.router=e||"jumpto",c.loop&&(0>a&&(a=c.group.length+a%c.group.length),a%=c.group.length),c.group[a]!==v&&(b.cancel(),b._start(a)))},reposition:function(a,d){var e=b.current,c=e?e.wrap:null,k;c&&(k=b._getPosition(d),a&&"scroll"===a.type?(delete k.position,c.stop(!0,!0).animate(k,200)):(c.css(k),e.pos=f.extend({},e.dim,k)))},update:function(a){var d=
|
||||
a&&a.type,e=!d||"orientationchange"===d;e&&(clearTimeout(B),B=null);b.isOpen&&!B&&(B=setTimeout(function(){var c=b.current;c&&!b.isClosing&&(b.wrap.removeClass("fancybox-tmp"),(e||"load"===d||"resize"===d&&c.autoResize)&&b._setDimension(),"scroll"===d&&c.canShrink||b.reposition(a),b.trigger("onUpdate"),B=null)},e&&!s?0:300))},toggle:function(a){b.isOpen&&(b.current.fitToView="boolean"===f.type(a)?a:!b.current.fitToView,s&&(b.wrap.removeAttr("style").addClass("fancybox-tmp"),b.trigger("onUpdate")),
|
||||
b.update())},hideLoading:function(){p.unbind(".loading");f("#fancybox-loading").remove()},showLoading:function(){var a,d;b.hideLoading();a=f('<div id="fancybox-loading"><div></div></div>').click(b.cancel).appendTo("body");p.bind("keydown.loading",function(a){if(27===(a.which||a.keyCode))a.preventDefault(),b.cancel()});b.defaults.fixed||(d=b.getViewport(),a.css({position:"absolute",top:0.5*d.h+d.y,left:0.5*d.w+d.x}))},getViewport:function(){var a=b.current&&b.current.locked||!1,d={x:n.scrollLeft(),
|
||||
y:n.scrollTop()};a?(d.w=a[0].clientWidth,d.h=a[0].clientHeight):(d.w=s&&r.innerWidth?r.innerWidth:n.width(),d.h=s&&r.innerHeight?r.innerHeight:n.height());return d},unbindEvents:function(){b.wrap&&t(b.wrap)&&b.wrap.unbind(".fb");p.unbind(".fb");n.unbind(".fb")},bindEvents:function(){var a=b.current,d;a&&(n.bind("orientationchange.fb"+(s?"":" resize.fb")+(a.autoCenter&&!a.locked?" scroll.fb":""),b.update),(d=a.keys)&&p.bind("keydown.fb",function(e){var c=e.which||e.keyCode,k=e.target||e.srcElement;
|
||||
if(27===c&&b.coming)return!1;!e.ctrlKey&&(!e.altKey&&!e.shiftKey&&!e.metaKey&&(!k||!k.type&&!f(k).is("[contenteditable]")))&&f.each(d,function(d,k){if(1<a.group.length&&k[c]!==v)return b[d](k[c]),e.preventDefault(),!1;if(-1<f.inArray(c,k))return b[d](),e.preventDefault(),!1})}),f.fn.mousewheel&&a.mouseWheel&&b.wrap.bind("mousewheel.fb",function(d,c,k,g){for(var h=f(d.target||null),j=!1;h.length&&!j&&!h.is(".fancybox-skin")&&!h.is(".fancybox-wrap");)j=h[0]&&!(h[0].style.overflow&&"hidden"===h[0].style.overflow)&&
|
||||
(h[0].clientWidth&&h[0].scrollWidth>h[0].clientWidth||h[0].clientHeight&&h[0].scrollHeight>h[0].clientHeight),h=f(h).parent();if(0!==c&&!j&&1<b.group.length&&!a.canShrink){if(0<g||0<k)b.prev(0<g?"down":"left");else if(0>g||0>k)b.next(0>g?"up":"right");d.preventDefault()}}))},trigger:function(a,d){var e,c=d||b.coming||b.current;if(c){f.isFunction(c[a])&&(e=c[a].apply(c,Array.prototype.slice.call(arguments,1)));if(!1===e)return!1;c.helpers&&f.each(c.helpers,function(d,e){if(e&&b.helpers[d]&&f.isFunction(b.helpers[d][a]))b.helpers[d][a](f.extend(!0,
|
||||
{},b.helpers[d].defaults,e),c)});p.trigger(a)}},isImage:function(a){return q(a)&&a.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i)},isSWF:function(a){return q(a)&&a.match(/\.(swf)((\?|#).*)?$/i)},_start:function(a){var d={},e,c;a=l(a);e=b.group[a]||null;if(!e)return!1;d=f.extend(!0,{},b.opts,e);e=d.margin;c=d.padding;"number"===f.type(e)&&(d.margin=[e,e,e,e]);"number"===f.type(c)&&(d.padding=[c,c,c,c]);d.modal&&f.extend(!0,d,{closeBtn:!1,closeClick:!1,nextClick:!1,arrows:!1,
|
||||
mouseWheel:!1,keys:null,helpers:{overlay:{closeClick:!1}}});d.autoSize&&(d.autoWidth=d.autoHeight=!0);"auto"===d.width&&(d.autoWidth=!0);"auto"===d.height&&(d.autoHeight=!0);d.group=b.group;d.index=a;b.coming=d;if(!1===b.trigger("beforeLoad"))b.coming=null;else{c=d.type;e=d.href;if(!c)return b.coming=null,b.current&&b.router&&"jumpto"!==b.router?(b.current.index=a,b[b.router](b.direction)):!1;b.isActive=!0;if("image"===c||"swf"===c)d.autoHeight=d.autoWidth=!1,d.scrolling="visible";"image"===c&&(d.aspectRatio=
|
||||
!0);"iframe"===c&&s&&(d.scrolling="scroll");d.wrap=f(d.tpl.wrap).addClass("fancybox-"+(s?"mobile":"desktop")+" fancybox-type-"+c+" fancybox-tmp "+d.wrapCSS).appendTo(d.parent||"body");f.extend(d,{skin:f(".fancybox-skin",d.wrap),outer:f(".fancybox-outer",d.wrap),inner:f(".fancybox-inner",d.wrap)});f.each(["Top","Right","Bottom","Left"],function(a,b){d.skin.css("padding"+b,w(d.padding[a]))});b.trigger("onReady");if("inline"===c||"html"===c){if(!d.content||!d.content.length)return b._error("content")}else if(!e)return b._error("href");
|
||||
"image"===c?b._loadImage():"ajax"===c?b._loadAjax():"iframe"===c?b._loadIframe():b._afterLoad()}},_error:function(a){f.extend(b.coming,{type:"html",autoWidth:!0,autoHeight:!0,minWidth:0,minHeight:0,scrolling:"no",hasError:a,content:b.coming.tpl.error});b._afterLoad()},_loadImage:function(){var a=b.imgPreload=new Image;a.onload=function(){this.onload=this.onerror=null;b.coming.width=this.width/b.opts.pixelRatio;b.coming.height=this.height/b.opts.pixelRatio;b._afterLoad()};a.onerror=function(){this.onload=
|
||||
this.onerror=null;b._error("image")};a.src=b.coming.href;!0!==a.complete&&b.showLoading()},_loadAjax:function(){var a=b.coming;b.showLoading();b.ajaxLoad=f.ajax(f.extend({},a.ajax,{url:a.href,error:function(a,e){b.coming&&"abort"!==e?b._error("ajax",a):b.hideLoading()},success:function(d,e){"success"===e&&(a.content=d,b._afterLoad())}}))},_loadIframe:function(){var a=b.coming,d=f(a.tpl.iframe.replace(/\{rnd\}/g,(new Date).getTime())).attr("scrolling",s?"auto":a.iframe.scrolling).attr("src",a.href);
|
||||
f(a.wrap).bind("onReset",function(){try{f(this).find("iframe").hide().attr("src","//about:blank").end().empty()}catch(a){}});a.iframe.preload&&(b.showLoading(),d.one("load",function(){f(this).data("ready",1);s||f(this).bind("load.fb",b.update);f(this).parents(".fancybox-wrap").width("100%").removeClass("fancybox-tmp").show();b._afterLoad()}));a.content=d.appendTo(a.inner);a.iframe.preload||b._afterLoad()},_preloadImages:function(){var a=b.group,d=b.current,e=a.length,c=d.preload?Math.min(d.preload,
|
||||
e-1):0,f,g;for(g=1;g<=c;g+=1)f=a[(d.index+g)%e],"image"===f.type&&f.href&&((new Image).src=f.href)},_afterLoad:function(){var a=b.coming,d=b.current,e,c,k,g,h;b.hideLoading();if(a&&!1!==b.isActive)if(!1===b.trigger("afterLoad",a,d))a.wrap.stop(!0).trigger("onReset").remove(),b.coming=null;else{d&&(b.trigger("beforeChange",d),d.wrap.stop(!0).removeClass("fancybox-opened").find(".fancybox-item, .fancybox-nav").remove());b.unbindEvents();e=a.content;c=a.type;k=a.scrolling;f.extend(b,{wrap:a.wrap,skin:a.skin,
|
||||
outer:a.outer,inner:a.inner,current:a,previous:d});g=a.href;switch(c){case "inline":case "ajax":case "html":a.selector?e=f("<div>").html(e).find(a.selector):t(e)&&(e.data("fancybox-placeholder")||e.data("fancybox-placeholder",f('<div class="fancybox-placeholder"></div>').insertAfter(e).hide()),e=e.show().detach(),a.wrap.bind("onReset",function(){f(this).find(e).length&&e.hide().replaceAll(e.data("fancybox-placeholder")).data("fancybox-placeholder",!1)}));break;case "image":e=a.tpl.image.replace("{href}",
|
||||
g);break;case "swf":e='<object id="fancybox-swf" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%"><param name="movie" value="'+g+'"></param>',h="",f.each(a.swf,function(a,b){e+='<param name="'+a+'" value="'+b+'"></param>';h+=" "+a+'="'+b+'"'}),e+='<embed src="'+g+'" type="application/x-shockwave-flash" width="100%" height="100%"'+h+"></embed></object>"}(!t(e)||!e.parent().is(a.inner))&&a.inner.append(e);b.trigger("beforeShow");a.inner.css("overflow","yes"===k?"scroll":
|
||||
"no"===k?"hidden":k);b._setDimension();b.reposition();b.isOpen=!1;b.coming=null;b.bindEvents();if(b.isOpened){if(d.prevMethod)b.transitions[d.prevMethod]()}else f(".fancybox-wrap").not(a.wrap).stop(!0).trigger("onReset").remove();b.transitions[b.isOpened?a.nextMethod:a.openMethod]();b._preloadImages()}},_setDimension:function(){var a=b.getViewport(),d=0,e=!1,c=!1,e=b.wrap,k=b.skin,g=b.inner,h=b.current,c=h.width,j=h.height,m=h.minWidth,u=h.minHeight,n=h.maxWidth,p=h.maxHeight,s=h.scrolling,q=h.scrollOutside?
|
||||
h.scrollbarWidth:0,x=h.margin,y=l(x[1]+x[3]),r=l(x[0]+x[2]),v,z,t,C,A,F,B,D,H;e.add(k).add(g).width("auto").height("auto").removeClass("fancybox-tmp");x=l(k.outerWidth(!0)-k.width());v=l(k.outerHeight(!0)-k.height());z=y+x;t=r+v;C=E(c)?(a.w-z)*l(c)/100:c;A=E(j)?(a.h-t)*l(j)/100:j;if("iframe"===h.type){if(H=h.content,h.autoHeight&&1===H.data("ready"))try{H[0].contentWindow.document.location&&(g.width(C).height(9999),F=H.contents().find("body"),q&&F.css("overflow-x","hidden"),A=F.outerHeight(!0))}catch(G){}}else if(h.autoWidth||
|
||||
h.autoHeight)g.addClass("fancybox-tmp"),h.autoWidth||g.width(C),h.autoHeight||g.height(A),h.autoWidth&&(C=g.width()),h.autoHeight&&(A=g.height()),g.removeClass("fancybox-tmp");c=l(C);j=l(A);D=C/A;m=l(E(m)?l(m,"w")-z:m);n=l(E(n)?l(n,"w")-z:n);u=l(E(u)?l(u,"h")-t:u);p=l(E(p)?l(p,"h")-t:p);F=n;B=p;h.fitToView&&(n=Math.min(a.w-z,n),p=Math.min(a.h-t,p));z=a.w-y;r=a.h-r;h.aspectRatio?(c>n&&(c=n,j=l(c/D)),j>p&&(j=p,c=l(j*D)),c<m&&(c=m,j=l(c/D)),j<u&&(j=u,c=l(j*D))):(c=Math.max(m,Math.min(c,n)),h.autoHeight&&
|
||||
"iframe"!==h.type&&(g.width(c),j=g.height()),j=Math.max(u,Math.min(j,p)));if(h.fitToView)if(g.width(c).height(j),e.width(c+x),a=e.width(),y=e.height(),h.aspectRatio)for(;(a>z||y>r)&&(c>m&&j>u)&&!(19<d++);)j=Math.max(u,Math.min(p,j-10)),c=l(j*D),c<m&&(c=m,j=l(c/D)),c>n&&(c=n,j=l(c/D)),g.width(c).height(j),e.width(c+x),a=e.width(),y=e.height();else c=Math.max(m,Math.min(c,c-(a-z))),j=Math.max(u,Math.min(j,j-(y-r)));q&&("auto"===s&&j<A&&c+x+q<z)&&(c+=q);g.width(c).height(j);e.width(c+x);a=e.width();
|
||||
y=e.height();e=(a>z||y>r)&&c>m&&j>u;c=h.aspectRatio?c<F&&j<B&&c<C&&j<A:(c<F||j<B)&&(c<C||j<A);f.extend(h,{dim:{width:w(a),height:w(y)},origWidth:C,origHeight:A,canShrink:e,canExpand:c,wPadding:x,hPadding:v,wrapSpace:y-k.outerHeight(!0),skinSpace:k.height()-j});!H&&(h.autoHeight&&j>u&&j<p&&!c)&&g.height("auto")},_getPosition:function(a){var d=b.current,e=b.getViewport(),c=d.margin,f=b.wrap.width()+c[1]+c[3],g=b.wrap.height()+c[0]+c[2],c={position:"absolute",top:c[0],left:c[3]};d.autoCenter&&d.fixed&&
|
||||
!a&&g<=e.h&&f<=e.w?c.position="fixed":d.locked||(c.top+=e.y,c.left+=e.x);c.top=w(Math.max(c.top,c.top+(e.h-g)*d.topRatio));c.left=w(Math.max(c.left,c.left+(e.w-f)*d.leftRatio));return c},_afterZoomIn:function(){var a=b.current;a&&(b.isOpen=b.isOpened=!0,b.wrap.css("overflow","visible").addClass("fancybox-opened"),b.update(),(a.closeClick||a.nextClick&&1<b.group.length)&&b.inner.css("cursor","pointer").bind("click.fb",function(d){!f(d.target).is("a")&&!f(d.target).parent().is("a")&&(d.preventDefault(),
|
||||
b[a.closeClick?"close":"next"]())}),a.closeBtn&&f(a.tpl.closeBtn).appendTo(b.skin).bind("click.fb",function(a){a.preventDefault();b.close()}),a.arrows&&1<b.group.length&&((a.loop||0<a.index)&&f(a.tpl.prev).appendTo(b.outer).bind("click.fb",b.prev),(a.loop||a.index<b.group.length-1)&&f(a.tpl.next).appendTo(b.outer).bind("click.fb",b.next)),b.trigger("afterShow"),!a.loop&&a.index===a.group.length-1?b.play(!1):b.opts.autoPlay&&!b.player.isActive&&(b.opts.autoPlay=!1,b.play()))},_afterZoomOut:function(a){a=
|
||||
a||b.current;f(".fancybox-wrap").trigger("onReset").remove();f.extend(b,{group:{},opts:{},router:!1,current:null,isActive:!1,isOpened:!1,isOpen:!1,isClosing:!1,wrap:null,skin:null,outer:null,inner:null});b.trigger("afterClose",a)}});b.transitions={getOrigPosition:function(){var a=b.current,d=a.element,e=a.orig,c={},f=50,g=50,h=a.hPadding,j=a.wPadding,m=b.getViewport();!e&&(a.isDom&&d.is(":visible"))&&(e=d.find("img:first"),e.length||(e=d));t(e)?(c=e.offset(),e.is("img")&&(f=e.outerWidth(),g=e.outerHeight())):
|
||||
(c.top=m.y+(m.h-g)*a.topRatio,c.left=m.x+(m.w-f)*a.leftRatio);if("fixed"===b.wrap.css("position")||a.locked)c.top-=m.y,c.left-=m.x;return c={top:w(c.top-h*a.topRatio),left:w(c.left-j*a.leftRatio),width:w(f+j),height:w(g+h)}},step:function(a,d){var e,c,f=d.prop;c=b.current;var g=c.wrapSpace,h=c.skinSpace;if("width"===f||"height"===f)e=d.end===d.start?1:(a-d.start)/(d.end-d.start),b.isClosing&&(e=1-e),c="width"===f?c.wPadding:c.hPadding,c=a-c,b.skin[f](l("width"===f?c:c-g*e)),b.inner[f](l("width"===
|
||||
f?c:c-g*e-h*e))},zoomIn:function(){var a=b.current,d=a.pos,e=a.openEffect,c="elastic"===e,k=f.extend({opacity:1},d);delete k.position;c?(d=this.getOrigPosition(),a.openOpacity&&(d.opacity=0.1)):"fade"===e&&(d.opacity=0.1);b.wrap.css(d).animate(k,{duration:"none"===e?0:a.openSpeed,easing:a.openEasing,step:c?this.step:null,complete:b._afterZoomIn})},zoomOut:function(){var a=b.current,d=a.closeEffect,e="elastic"===d,c={opacity:0.1};e&&(c=this.getOrigPosition(),a.closeOpacity&&(c.opacity=0.1));b.wrap.animate(c,
|
||||
{duration:"none"===d?0:a.closeSpeed,easing:a.closeEasing,step:e?this.step:null,complete:b._afterZoomOut})},changeIn:function(){var a=b.current,d=a.nextEffect,e=a.pos,c={opacity:1},f=b.direction,g;e.opacity=0.1;"elastic"===d&&(g="down"===f||"up"===f?"top":"left","down"===f||"right"===f?(e[g]=w(l(e[g])-200),c[g]="+=200px"):(e[g]=w(l(e[g])+200),c[g]="-=200px"));"none"===d?b._afterZoomIn():b.wrap.css(e).animate(c,{duration:a.nextSpeed,easing:a.nextEasing,complete:b._afterZoomIn})},changeOut:function(){var a=
|
||||
b.previous,d=a.prevEffect,e={opacity:0.1},c=b.direction;"elastic"===d&&(e["down"===c||"up"===c?"top":"left"]=("up"===c||"left"===c?"-":"+")+"=200px");a.wrap.animate(e,{duration:"none"===d?0:a.prevSpeed,easing:a.prevEasing,complete:function(){f(this).trigger("onReset").remove()}})}};b.helpers.overlay={defaults:{closeClick:!0,speedOut:200,showEarly:!0,css:{},locked:!s,fixed:!0},overlay:null,fixed:!1,el:f("html"),create:function(a){a=f.extend({},this.defaults,a);this.overlay&&this.close();this.overlay=
|
||||
f('<div class="fancybox-overlay"></div>').appendTo(b.coming?b.coming.parent:a.parent);this.fixed=!1;a.fixed&&b.defaults.fixed&&(this.overlay.addClass("fancybox-overlay-fixed"),this.fixed=!0)},open:function(a){var d=this;a=f.extend({},this.defaults,a);this.overlay?this.overlay.unbind(".overlay").width("auto").height("auto"):this.create(a);this.fixed||(n.bind("resize.overlay",f.proxy(this.update,this)),this.update());a.closeClick&&this.overlay.bind("click.overlay",function(a){if(f(a.target).hasClass("fancybox-overlay"))return b.isActive?
|
||||
b.close():d.close(),!1});this.overlay.css(a.css).show()},close:function(){var a,b;n.unbind("resize.overlay");this.el.hasClass("fancybox-lock")&&(f(".fancybox-margin").removeClass("fancybox-margin"),a=n.scrollTop(),b=n.scrollLeft(),this.el.removeClass("fancybox-lock"),n.scrollTop(a).scrollLeft(b));f(".fancybox-overlay").remove().hide();f.extend(this,{overlay:null,fixed:!1})},update:function(){var a="100%",b;this.overlay.width(a).height("100%");I?(b=Math.max(G.documentElement.offsetWidth,G.body.offsetWidth),
|
||||
p.width()>b&&(a=p.width())):p.width()>n.width()&&(a=p.width());this.overlay.width(a).height(p.height())},onReady:function(a,b){var e=this.overlay;f(".fancybox-overlay").stop(!0,!0);e||this.create(a);a.locked&&(this.fixed&&b.fixed)&&(e||(this.margin=p.height()>n.height()?f("html").css("margin-right").replace("px",""):!1),b.locked=this.overlay.append(b.wrap),b.fixed=!1);!0===a.showEarly&&this.beforeShow.apply(this,arguments)},beforeShow:function(a,b){var e,c;b.locked&&(!1!==this.margin&&(f("*").filter(function(){return"fixed"===
|
||||
f(this).css("position")&&!f(this).hasClass("fancybox-overlay")&&!f(this).hasClass("fancybox-wrap")}).addClass("fancybox-margin"),this.el.addClass("fancybox-margin")),e=n.scrollTop(),c=n.scrollLeft(),this.el.addClass("fancybox-lock"),n.scrollTop(e).scrollLeft(c));this.open(a)},onUpdate:function(){this.fixed||this.update()},afterClose:function(a){this.overlay&&!b.coming&&this.overlay.fadeOut(a.speedOut,f.proxy(this.close,this))}};b.helpers.title={defaults:{type:"float",position:"bottom"},beforeShow:function(a){var d=
|
||||
b.current,e=d.title,c=a.type;f.isFunction(e)&&(e=e.call(d.element,d));if(q(e)&&""!==f.trim(e)){d=f('<div class="fancybox-title fancybox-title-'+c+'-wrap">'+e+"</div>");switch(c){case "inside":c=b.skin;break;case "outside":c=b.wrap;break;case "over":c=b.inner;break;default:c=b.skin,d.appendTo("body"),I&&d.width(d.width()),d.wrapInner('<span class="child"></span>'),b.current.margin[2]+=Math.abs(l(d.css("margin-bottom")))}d["top"===a.position?"prependTo":"appendTo"](c)}}};f.fn.fancybox=function(a){var d,
|
||||
e=f(this),c=this.selector||"",k=function(g){var h=f(this).blur(),j=d,k,l;!g.ctrlKey&&(!g.altKey&&!g.shiftKey&&!g.metaKey)&&!h.is(".fancybox-wrap")&&(k=a.groupAttr||"data-fancybox-group",l=h.attr(k),l||(k="rel",l=h.get(0)[k]),l&&(""!==l&&"nofollow"!==l)&&(h=c.length?f(c):e,h=h.filter("["+k+'="'+l+'"]'),j=h.index(this)),a.index=j,!1!==b.open(h,a)&&g.preventDefault())};a=a||{};d=a.index||0;!c||!1===a.live?e.unbind("click.fb-start").bind("click.fb-start",k):p.undelegate(c,"click.fb-start").delegate(c+
|
||||
":not('.fancybox-item, .fancybox-nav')","click.fb-start",k);this.filter("[data-fancybox-start=1]").trigger("click");return this};p.ready(function(){var a,d;f.scrollbarWidth===v&&(f.scrollbarWidth=function(){var a=f('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo("body"),b=a.children(),b=b.innerWidth()-b.height(99).innerWidth();a.remove();return b});if(f.support.fixedPosition===v){a=f.support;d=f('<div style="position:fixed;top:20px;"></div>').appendTo("body");var e=20===
|
||||
d[0].offsetTop||15===d[0].offsetTop;d.remove();a.fixedPosition=e}f.extend(b.defaults,{scrollbarWidth:f.scrollbarWidth(),fixed:f.support.fixedPosition,parent:f("body")});a=f(r).width();J.addClass("fancybox-lock-test");d=f(r).width();J.removeClass("fancybox-lock-test");f("<style type='text/css'>.fancybox-margin{margin-right:"+(d-a)+"px;}</style>").appendTo("head")})})(window,document,jQuery);
|
||||
@@ -0,0 +1,758 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<title>piPlayer</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<meta property="og:image" content="http://www.tilman.de/projekte/piplayer/photos/02.jpg" />
|
||||
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="fancybox/source/jquery.fancybox.css?v=2.1.5" type="text/css" media="screen" />
|
||||
<script type="text/javascript" src="fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
|
||||
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,800' rel='stylesheet' type='text/css' />
|
||||
<link href='https://fonts.googleapis.com/css?family=Lora:400,400italic,700,700italic' rel='stylesheet' type='text/css' />
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
|
||||
<style type="text/css" media="all">
|
||||
<!--
|
||||
body {
|
||||
background-color: #FFFFFF;
|
||||
color: black;
|
||||
font-family: 'Lora', serif;
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 800;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
#subtitle {
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 800;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
color: #303030;
|
||||
font-size: 120%;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
text-align: right;
|
||||
color: #888;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.fancybox-title {
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
-->
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(".fancybox").fancybox({
|
||||
openEffect : 'none',
|
||||
closeEffect : 'none',
|
||||
helpers : {
|
||||
title : {
|
||||
type : 'inside'
|
||||
}
|
||||
},
|
||||
beforeShow : function() {
|
||||
var alt = this.element.find('img').attr('alt');
|
||||
this.inner.find('img').attr('alt', alt);
|
||||
this.title = alt;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content" class="container">
|
||||
<p class="timestamp">2017-01-08</p>
|
||||
<h1>Die Musikrakete</h1>
|
||||
<p id="subtitle">Ein kindgerechter mp3-Player auf Basis von Raspberry Pi und JBL OnStage IIIp</p>
|
||||
|
||||
<p style="font-weight: 700;">Der CD-Player im Kinderzimmer war zwar beliebt, aber nicht wirklich praktisch: Die CDs waren so
|
||||
schnell zerkratzt, dass ich mit dem nachbrennen kaum hinterherkam und auch das Gerät selbst schien schon nach einem dreiviertel
|
||||
Jahr seinem Ende entgegen zu gehen. Deshalb sollte eine neue Lösung her. Möglichst robust, ohne CDs und auf jeden Fall ohne
|
||||
Hilfe der Eltern bedienbar.</p>
|
||||
|
||||
<p class="gallery" style="float:right;">
|
||||
<a href="photos/02.jpg" class="fancybox"><img src="photos/02.jpg"
|
||||
width="480" alt="Die Musikrakete. Durch den Schlitz in der Mitte werden Karten mit QR-Codes eingesteckt, der Player spielt dann das entsprechende Album ab. Mit den roten Schaltern kann man zwischen den Titeln springen und stoppen." /></a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Es fing damit an, dass Anica mich mit Begeisterung auf den <a href="https://de-de.hoerbert.com/">hörbert</a> aufmerksam
|
||||
machte. Das ist ein mp3-Player für Kinder, genauer gesagt ein Mono-Lautsprecher im Holzgehäuse mit einem SD-Slot und vier
|
||||
AA-Batterien. An der Vorderseite können bis zu neun Tracks über bunte Tasten direkt ausgewählt werden.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Ich war nicht so überzeugt von dem Konzept. Die SD-Karte muss man für jeden Wechsel der Inhalte herausnehmen, Batteriebetrieb
|
||||
fand ich nicht gut und so richtig günstig kam mir die Sache auch nicht vor. Allerdings war ich kurz vorher auf die Projektseite
|
||||
für einen <a href="http://www.die-schembergs.de/index.php?page=qr-music">MP3-Player für Kindergartenkinder</a> gestoßen und
|
||||
habe das als Gegenbeispiel angeführt: Ein kleiner Raspberry Pi wird mit einer Webcam und ein paar Lautsprechern verbunden. Auf
|
||||
Knopfdruck wird ein QR-Code gelesen, den das Kind unter die Webcam gelegt hat und daraufhin wird das entsprechende Album
|
||||
abgespielt.
|
||||
</p>
|
||||
|
||||
<p>Entgegen meiner Erwartung wurde das gleich für besser als der hörbert befunden und damit hatte ich mein Projekt.</p>
|
||||
|
||||
<p>
|
||||
Weil der grundsätzliche Aufbau nicht besonders kompliziert ist, bin ich ursprünglich davon ausgegangen, dass sich das Ganze mit
|
||||
zwei bis drei Wochenenden umsetzen lassen müsste. Für die Basis-Variante war diese Einschätzung wohl auch nicht falsch, aber
|
||||
wie bei jedem guten Projekt kam dann einiges an Fehlversuchen, Verzögerungen und auch etwas <em>Feature Creep</em> hinzu. Am
|
||||
Ende war ich um einiges an Erfahrung reicher, habe viel dazu gelernt (CAD, Python) und aus „ein bisschen Elektronik in einer
|
||||
Box“ wurde eine Musikrakete aus dem 3D-Drucker. Der ursprünglich gesetzte Kostenrahmen wurde gnadenlos überzogen (ebenfalls wie
|
||||
bei jedem guten Projekt) und von den ersten Überlegungen bis zum glorreichen Moment der Übergabe an die Endnutzer vergingen
|
||||
dann auch fast vier Monate.
|
||||
</p>
|
||||
|
||||
<p>Der Player ist ein voller Erfolg und wurde von den Jungs super angenommen. Neben ein paar Verbesserungen an der Software
|
||||
plane ich noch eine Anleitung für eine kostengünstigere Version. Es gibt da draußen noch viele Lautsprecher mit altem
|
||||
Apple-Dock-Anschluss, die auf ein neues Leben warten.</p>
|
||||
|
||||
<div style="margin: 2em; text-align: center;">
|
||||
<video controls><source src="musikrakete.mp4" type="video/mp4" /></video>
|
||||
</div>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/2016-03-25_08-25-01.jpg" class="fancybox" rel="strecke02"><img src="fotos/2016-03-25_08-25-01.jpg" width="320"
|
||||
alt="Der Player wurde gut angenommen. Das Bedienprinzip war sofort klar und wurde ausgiebig erprobt." /></a> <a
|
||||
href="fotos/IMG_0449_cut_rot.JPG" class="fancybox" rel="strecke02"><img src="fotos/IMG_0449_cut_rot.JPG" width="320"
|
||||
alt="Großer Andrang: Auch dem Einjährigen war schnell klar, dass die Karte die Musik macht. Der Player würde seine Begeisterung wahrscheinlich sogar aushalten, aber die QR-Karten sollten möglichst nicht gekaut werden, außerdem möchte der große Bruder seine Hörspiele gerne bis zum Schluss hören. Deshalb wurde das Gerät erstmal außer Reichweite positioniert. Dafür hat der Kleine jetzt Exklusiv-Zugriff auf den alten CD-Player." /></a>
|
||||
<a href="fotos/2016-04-02 09-36-39.jpg" class="fancybox" rel="strecke02"><img src="fotos/2016-04-02 09-36-39.jpg"
|
||||
width="320"
|
||||
alt="Die QR-Codes sind streifenweise auf DIN A4 gedruckt und danach einmal gefaltet, so dass der QR-Code auf der Unterseite und das Album-Cover auf der Oberseite ist." /></a>
|
||||
</p>
|
||||
<hr />
|
||||
|
||||
<p style="margin-top: 3em; font-style: italic;">Für alle, die es genau wissen wollen und als Dokumentation noch der
|
||||
Projektverlauf mit allen Details:</p>
|
||||
|
||||
<h2 id="planung">Planung und erste Schritte</h2>
|
||||
|
||||
<p>
|
||||
Mir war schnell klar, dass ich gegenüber dem <a
|
||||
href="http://www.raspberry-pi-geek.de/Magazin/2014/03/Kindgerechter-Audioplayer-mit-dem-Raspberry-Pi/%28language%29/ger-DE">Originaldesign</a>
|
||||
ein paar Verbesserungen wollte: Auf jeden Fall musste der Player eine Einheit sein, also alles in einem Gehäuse verbaut werden,
|
||||
inklusive der Lautsprecher. Zweitens wollte ich eine automatische Erkennung der QR-Codes durch eine Lichtschranke ohne
|
||||
zusätzliches Drücken irgendwelcher Knöpfe. Und drittens musste die Musik nicht von der internen SD-Karte sondern von unserem
|
||||
Heimserver kommen.
|
||||
</p>
|
||||
|
||||
<p class="gallery" style="padding-left: 1em; float: right;">
|
||||
<!--
|
||||
<a href="images/Stuetzstruktur.jpg" class="fancybox" rel="bilder-stuetzstruktur"><img
|
||||
src="images/Stuetzstruktur.jpg" width="320"
|
||||
alt="Erster Ansatz: Auf dieser Stützstruktur sollten der Raspi und die Webcam montiert werden. Vorne im Bild das Panel für die Montage der Schalter mit dem Schlitz für die QR-Code-Karten. Leider spielte die Kunststoff-Kugel, die alles beherbergen sollte, nicht mit." /></a>
|
||||
-->
|
||||
<a href="fotos/2016-01-06 11-51-22_cut.jpg" class="fancybox" rel="bilder-stuetzstruktur"><img
|
||||
src="fotos/2016-01-06 11-51-22_cut.jpg" width="320"
|
||||
alt="Erster Ansatz: Auf dieser Stützstruktur sollten der Raspi und die Webcam montiert werden. Vorne im Bild das Panel für die Montage der Schalter mit dem Schlitz für die QR-Code-Karten. Leider spielte die Kunststoff-Kugel, die alles beherbergen sollte, nicht mit." /></a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Nachdem ich mir ein paar Gedanken über den Gehäusebau gemacht hatte, fiel mir ein <a
|
||||
href="http://eu.jbl.com/jbl_product_detail_de/jblos3pipwhtev.html">JBL OnStage IIIP</a> in die Hände, der seit einiger Zeit
|
||||
bei uns einsam im Schrank lag. Der alte 30-Pin-Anschluss für iPods ist inzwischen nicht mehr ganz aktuell und die passenden
|
||||
Geräte schon nicht mehr im Einsatz. Ich wusste aber noch, dass der Lautsprecher einen beeindruckend guten Klang hat, außerdem
|
||||
passte die Größe gut zum Projekt. Die Frage war jetzt also, wie man die Komponenten (Raspi, Webcam, Lichtschranke, etc.) gut
|
||||
auf die kleine Plattform bekommen könnte.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Der ursprüngliche Ansatz war, eine Halterung für die Bauteile drucken zu lassen und dann alles zusammen in die Kunstoff-Kugel
|
||||
von einer Gartenleuchte (<a href="https://www.hitmeister.de/product/301407134/">wie dieser</a>) einzubauen. Die Idee kam
|
||||
dadurch zustande, dass die Form des JBL-Lautsprechers genau das Aufsetzen einer Kugel mit 20 cm Durchmesser erlaubt.
|
||||
</p>
|
||||
|
||||
<p>Leider war die Kugel weit weniger formstabil als angenommen. Beim Einschneiden fiel sie praktisch sofort in sich zusammen
|
||||
und war damit unbrauchbar. Außerdem ließ sich das Problem der Befestigung der Kugel auf der Plattform des Lautsprechers nicht
|
||||
so einfach lösen, so dass dieser erste Versuch dem Player ein Gehäuse zu geben fehlschlug. Deshalb sollte im zweiten Anlauf
|
||||
gleich das ganze Gehäuse gedruckt werden.</p>
|
||||
|
||||
<h2 id="design">Design</h2>
|
||||
|
||||
<p>
|
||||
Für den Gehäuse-Entwurf kam <a href="https://www.onshape.com/">Onshape</a> zum Einsatz. Ich hatte vorher noch mit FreeCAD
|
||||
experimentiert, war aber nicht wirklich voran gekommen. Onshape stürzte im Browser zwar immer mal wieder ab, aber da jeder
|
||||
Klick sofort gespeichert wird war das nicht besonders problematisch. Auf jeden Fall war die Bedienung so intuitiv, dass auch
|
||||
ein absoluter CAD-Anfänger wie ich schnell zurecht kommen konnte.
|
||||
</p>
|
||||
|
||||
<p>Das Gehäuse sollte einer Rakete nachempfunden sein. Zum einen, weil Raketen von Dreijährigen im Allgemeinen gut
|
||||
angenommen werden, zum anderen, weil die langgestreckte Form den Einbau eines WLAN-Adapters mit Antenne ermöglicht, der auch im
|
||||
schlecht abgedeckten Kinderzimmer noch ausreichenden Empfang garantiert.</p>
|
||||
|
||||
<hr/>
|
||||
<p class="gallery">
|
||||
<a href="images/Musikrakete-CAD-front.jpg" class="fancybox" rel="strecke01"><img src="images/Musikrakete-CAD-front.jpg"
|
||||
width="320" alt="Das Gehäuse von vorne" /></a>
|
||||
<a href="images/piplayer-montage-480.gif" class="fancybox" rel="strecke01"><img src="images/piplayer-montage-480.gif" width="320"
|
||||
alt="Montage der Einzelteile" /></a>
|
||||
<a href="images/Musikrakete-CAD-trimetric.jpg" class="fancybox"
|
||||
rel="strecke01"><img src="images/Musikrakete-CAD-trimetric.jpg" width="320"
|
||||
alt="Von schräg oben sieht man gut die Öffnungen für die Schalter und den Infrarot-Sensor" /></a>
|
||||
</p>
|
||||
<hr/>
|
||||
|
||||
<p>
|
||||
Da ich vor dem Projekt weder Erfahrung mit CAD noch mit 3D-Druck hatte, musste ich erst ein paar <a
|
||||
href="http://www.slideshare.net/en0tjh/design-for-ultimaker-rev2">Grundlagen</a> lernen. Um nicht druckbare Überhänge zu
|
||||
vermeiden wurde die Rakete in drei Segmente unterteilt, die später verschraubt werden sollten. Ein Check der fertigen
|
||||
STL-Dateien mit <a href="http://www.willit3dprint.com/">Willit? 3D Print</a> gab nochmals zusätzliche Sicherheit. Durch die
|
||||
Entscheidung, das gesamte Gehäuse zu drucken war der ursprüngliche Kostenrahmen sowieso schon hinfällig, mehrere Druckversuche
|
||||
für die Hauptkomponenten wären schlicht zu teuer geworden.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Das Raketen-Gehäuse besteht aus drei Segmenten: Unten der Hauptteil, in dem der Raspi verschraubt und die Webcam installiert
|
||||
wird. Darauf wird die Schiene angebracht, auf der die Karten mit den QR-Codes eingeschoben werden, sowie der Mittelteil, der
|
||||
ein kleines Fenster für einen Infrarot-Empfänger hat. Dadurch kann der Player auch per Fernbedienung gesteuert werden. Ganz
|
||||
oben dann die Raketenspitze mit Öffnungen für drei Schalter, die mit den Funktionen <em>Vor</em>, <em>Zurück</em> und <em>Play/Pause</em>
|
||||
belegt werden. Damit das Ganze auch wirklich nach Rakete aussieht, werden links und rechts noch Leitwerk-Elemente angeschraubt.
|
||||
Den Druck der fertigen Rakete übernahmen die Jungs von <a href="http://www.laydrop.com/">Laydrop</a> (via <a
|
||||
href="https://www.3dhubs.com/berlin/hubs/laydrop">3dhub.com</a>). Material PLA, Schichtdicke 300, Preis 105,80 € (und damit
|
||||
bei weitem die teuerste Komponente).
|
||||
</p>
|
||||
|
||||
<!-- p class="gallery">
|
||||
<a href="images/piplayer-montage-480.gif" class="fancybox"><img src="images/piplayer-montage-480.gif" width="480"
|
||||
alt="Das fertige Gehäuse" /></a>
|
||||
</p -->
|
||||
|
||||
<p>
|
||||
Die Erfahrungen aus dem ersten Anlauf mit der Stützstruktur machten sich bezahlt. Bis auf ein paar Kleinigkeiten passte alles
|
||||
exakt. Das Ganze wurde dann auf dem JBL-Lautsprecher verschraubt. Die Löcher für die Schrauben hatte ich ganz zu Anfang in den
|
||||
Lautsprecher gebohrt und dann das Gehäuse drum herum entworfen. Beim Öffnen half eine <a
|
||||
href="https://de.ifixit.com/Guide/Disassembling+the+Circuit+Board/22634">Anleitung von ifixit.com</a> (für den On Stage II,
|
||||
aber der ist hinreichend ähnlich aufgebaut).
|
||||
</p>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/2015-12-10 10-31-58_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2015-12-10 10-31-58_cut.jpg" width="160"
|
||||
alt="Der JBL On Stage IIIp vor der Öffnung. Links oben im Bild die Kunststoff-Kugel, die ursprünglich als Gehäuse dienen sollte." /></a>
|
||||
<a href="fotos/2015-12-10 11-06-20_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2015-12-10 11-06-20_cut.jpg" width="160"
|
||||
alt="Um an die Schrauben zu kommen müssen die Gummifüße mit einem Fön oder einer Heißluftpistole abgelöst werden." /></a>
|
||||
<a href="fotos/2015-12-10 11-10-12_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2015-12-10 11-10-12_cut.jpg" width="160"
|
||||
alt="Sobald alle Schrauben gelöst sind, lässt sich das Gehäust problemlos auseinander nehmen. Da sich die Platine nicht so einfach entnehmen lässt, musste ich die Schrauben dort setzen, wo sie die Plattform nicht verdeckt." /></a>
|
||||
<a href="fotos/2015-12-10 12-05-35_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2015-12-10 12-05-35_cut.jpg" width="160"
|
||||
alt="Die Löcher für die M3 Schrauben wurden mit einem M2.5 Bohrer gebohrt, dadurch sitzt alles recht fest. Dabei ganz wichtig: Die Bohrmaschine muss auf Linkslauf gestellt werden (also entgegen der Schneiderichtung). Dadurch wird das Loch eher geschmolzen als gebohrt und der Kunststoff bricht nicht." /></a>
|
||||
<a href="fotos/2015-12-10 12-37-42_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2015-12-10 12-37-42_cut.jpg" width="160"
|
||||
alt="Die Schrauben sitzen fest und geben dem Gehäuse guten Halt. (Auf dem Bild fehlt noch die vierte Schraube im Mittelpunkt der Plattform.)" /></a>
|
||||
<a href="fotos/2016-01-27 16-31-39_cut.jpg" class="fancybox" rel="fotos-bohren"><img
|
||||
src="fotos/2016-01-27 16-31-39_cut.jpg" width="160"
|
||||
alt="Durch die Unterlegscheiben verteilt sich der Druck noch etwas besser. Die Schraube links unten ist allerdings so eng gesetzt, dass keine Unterlegscheibe mehr Platz hatte." /></a>
|
||||
</p>
|
||||
|
||||
<h2 id="aufbau">Aufbau</h2>
|
||||
|
||||
<h3 id="stueckliste">Stückliste</h3>
|
||||
|
||||
<ul>
|
||||
<li>JBL On Stage IIIP (gebraucht für unter 20 € zu bekommen)</li>
|
||||
<li>fünfteiliges <a
|
||||
href="https://cad.onshape.com/documents/56f3cf83e4b02f6312a93771/w/785ccf8684baed006305bb0d/e/a314638959d0c9d7ea39d770">Raketen-Gehäuse</a> aus dem 3D-Drucker (<a href="https://github.com/tliero/musikrakete/tree/master/case">STL-Dateien</a> auch im <a href="https://github.com/tliero/musikrakete">github-Repository</a>)</li>
|
||||
<li>Raspberry Pi 2 Model B, 34 €</li>
|
||||
<li>Webcam für ca. 2 € über ebay</li>
|
||||
<li>USB-Wlan-Adapter mit Stabantenne, ca. 5 €</li>
|
||||
<li>USB-Audio-Adapter für ca. 2 € über ebay</li>
|
||||
<li>USB-Netzteil, 2,1 A</li>
|
||||
<li>Kabel und Adapter
|
||||
<ul>
|
||||
<li>30-Pin Apple Dock auf Micro-USB und 3,5 mm Klinkenstecker, ca. 2 €</li>
|
||||
<li>10 cm 3,5 mm AUX Audiokabel</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Elektronik-Bauteile
|
||||
<ul>
|
||||
<li>3x 12 mm Drucktaster ("Momentary Push Button")</li>
|
||||
<li>Infrarot-Sensor VS1838B als Empfänger für die Fernbedienung</li>
|
||||
<li>Lichtschranke (Emitter und Sensor <a href="http://www.pollin.de/shop/dt/NzA0OTc4OTk-/Bauelemente_Bauteile/Aktive_Bauelemente/Optoelektronik/Infrarot_Lichtschranken_Paare_TEMIC_K153P.html">TEMIC K153P</a>)</li>
|
||||
<li>Lochraster-Platine</li>
|
||||
<li>5 mm LED, diffus, 16000 mcd</li>
|
||||
<li>Widerstände: 2x 470 Ohm, 1x 100 kOhm</li>
|
||||
<li>Schaltdraht 0,5mm</li>
|
||||
<li>Steckbrücken/Jumper-Kabel</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Befestigungsmaterial
|
||||
<ul>
|
||||
<li>3x 20 mm M3 Schraube + Mutter + Unterlegscheibe, um das Gehäuse am Lautsprecher zu befestigen</li>
|
||||
<li>1x 40 mm M3 Schraube + Mutter + Unterlegscheibe, um das Gehäuse am Lautsprecher zu befestigen (Schraube in
|
||||
der Mitte der Plattform)</li>
|
||||
<li>11x 12 mm M3 Schraube + Mutter, für den Zusammenbau der Raketenteile</li>
|
||||
<li>8x M2.5 Schraube + Mutter (Kunststoff), zum Befestigen des Raspberry Pi und der Leitwerke</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Sonstiges
|
||||
<ul>
|
||||
<li>Polymorph (für den Kamerasockel und Modifikationen an den Kabeln) von <a href="http://www.knetfix.de">knetfix.de</a></li>
|
||||
<li>ein Stück Acrylglas für das Fenster vor dem Infrarotsensor</li>
|
||||
<li>eine alte 3.5"-Diskette, um das Fenster abzudunkeln</li>
|
||||
<li>Werkzeug: Bohrmaschine, Multimeter, Heißklebepistole, Lötkolben, Schraubendreher, Zange, Messer</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="elektronik">Elektronik und Montage</h3>
|
||||
|
||||
<p>
|
||||
Parallel zum CAD-Design musste noch die Elektronik zusammengesetzt werden. Zum Glück war der Aufbau mit einer <a
|
||||
href="http://tutorials-raspberrypi.de/infrarot-lichtschranke-bauen-und-geschwindigkeit-messen/">Lichtschranke</a>, einer
|
||||
<a href="https://raspuino.wordpress.com/2014/03/24/raspberry-pi-led-blinken-lassen-mit-python/">Beleuchtungs-LED</a>, drei <a
|
||||
href="http://razzpisampler.oreilly.com/ch07.html">Schaltern</a> und dem <a
|
||||
href="http://powerpi.de/jede-infrarot-fernbedienung-am-raspberry-pi-2-nutzen-so-installierst-du-guenstig-einen-ir-empfaenger-teil-1/">Infrarot-Empfänger</a> nicht allzu kompliziert, für alle Elemente finden sich gute Anleitungen im Netz.
|
||||
Der fertige Aufbau ist auf dem Schemabild unten zu sehen. Verlötet wurde alles auf einer kleinen Leiterplatte, die mit auf
|
||||
dem Rahmen in der Mitte der Rakete sitzt.
|
||||
</p>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/IMG_20160203_175256_cut.jpg" class="fancybox" rel="elektronik"><img src="fotos/IMG_20160203_175256_cut.jpg"
|
||||
width="320"
|
||||
alt="Testaufbau: Bevor die Elektronik verlötet wurde, musste erst alles auf einem Breadboard ausgetestet werden. Hier die LED für die Beleuchtung zusammen mit der Lichtschranke." /></a>
|
||||
<a href="images/Elektronik_Steckplatine.png" class="fancybox" rel="elektronik"><img
|
||||
src="images/Elektronik_Steckplatine.png" width="480"
|
||||
alt="Der komplette Aufbau der Elektronik mit Lichtschranke und Beleuchtung (links), Schaltern (mitte) und IR-Empfänger für die Fernbedienung (rechts)." /></a>
|
||||
<a href="fotos/2016-04-02 09-11-16.jpg" class="fancybox" rel="elektronik"><img
|
||||
src="fotos/2016-04-02 09-11-16.jpg" width="320"
|
||||
alt="Alle Bauteile wurden auf einer Platine verlötet und auf den Rahmen geklebt. Die Lichtschranke ist zusätzlich mit Polymorph fixiert, damit man die Drähte nicht beim Einschieben der QR-Code-Karten verbiegt." /></a>
|
||||
</p>
|
||||
|
||||
<p>Die gedruckten Raketen-Elemente passten gut und mussten nur zusammengeschraubt und auf dem Lautsprecher montiert werden. Schwieriger
|
||||
war die Befestigung und Ausrichtung der Webcam. Letztendlich half hier Polymorph-Kunststoff weiter: Im ersten Schritt wurde ein
|
||||
Sockel geformt, der in der Mitte des Raketenbodens sitzt und sich sich in die Vertiefung des iPod-Anschlusses einfügt. Dadurch
|
||||
sitzt die Kamera ausreichend fest und hat eine genau definierte Perspektive, kann aber jederzeit für Arbeiten am Gerät
|
||||
entnommen werden. Im zweiten Schritt wurde dann die Kamera mit Polymorph auf dem Sockel fixiert. Dabei war sie mit einem Laptop
|
||||
verbunden, so dass die Perpektive kontrolliert und nachkorrigiert werden konnte, bis die Verbindung ausgehärtet war.</p>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/IMG_20160208_111601_cut.jpg" class="fancybox" rel="fotos-kamerasockel"><img
|
||||
src="fotos/IMG_20160208_111601_cut.jpg" width="320"
|
||||
alt="Zuerst wurde aus Polymorph ein Sockel geformt, der sich so über den Rand der Bodenöffnung legt, dass die Kamera nicht verrutschen kann. Dabei ist wichtig, dass das Polymorph noch formbar, aber schon nicht mehr klebrig ist. Rechts oben im Bild sieht man den mit Polymorph abgewinkelten Micro-USB-Stecker." /></a>
|
||||
<a href="fotos/IMG_20160208_112329_cut.jpg" class="fancybox" rel="fotos-kamerasockel"><img
|
||||
src="fotos/IMG_20160208_112329_cut.jpg" width="320"
|
||||
alt="Der zweite Schritt war dann die Befestigung der Kamera auf dem Sockel. Dabei war sie an einen externen Computer angeschlossen, so dass die Ausrichtung korrekt eingestellt werden konnte (mit guvcview)." /></a>
|
||||
</p>
|
||||
|
||||
<p>Der Polymorph-Kunststoff war auch bei der Bearbeitung der Kabel nützlich: Der Micro-USB-Anschluss des Raspi war so nah an
|
||||
der Außenwand der Rakete, dass sich das Kabel nicht mehr ohne Aus- und Wiedereinbau einstecken ließ. Anstatt extra Adapter oder Kabel mit 90°-Winkel
|
||||
nachzukaufen wurde die Plastik-Kapsel am Stecker entfernt und durch Polymorph ersetzt. Damit kam das Standard-Kabel schnell und
|
||||
billig zu seinem 90°-Winkel.</p>
|
||||
|
||||
<p>Eine weitere kleine Herausforderung war noch das Fenster für den Infrarot-Sensor. Nach erfolgloser Suche nach geeignetem
|
||||
dunklen Kunststoff (wie er auch vorne an Fernbedienungen oder auf Empfängerseite an Stereoanlagen verwendet wird), fand ich in einem Forum den Hinweis, dass die dunklen
|
||||
Plastikscheiben aus Disketten in etwa dieselben Eigenschaften haben. Ein Stückchen Diskettenplastik auf einer Acrylscheibe und
|
||||
mit Heißkleber hinter die Fenster-Öffnung geklebt - fertig.</p>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/IMG_20160210_145935_cut.jpg" class="fancybox" rel="IR-Abdeckung"><img
|
||||
src="fotos/IMG_20160210_145935_cut.jpg" width="320"
|
||||
alt="Vor das Stück aus der Diskette kommt noch ein Stück Acrylglas, damit nicht der erste Finger, der die Konstruktion testet, gleich durchsticht. Zusammengehalten werden die beiden Zeile mit Polymorph." /></a>
|
||||
<a href="fotos/2016-04-02 09-20-19.jpg" class="fancybox" rel="IR-Abdeckung"><img src="fotos/2016-04-02 09-20-19.jpg" width="320" alt="Die Fensterscheibe ist zusammen mit dem Infrarot-Empfänger mit Heißkleber hinter die Öffnung geklebt." /></a>
|
||||
</p>
|
||||
|
||||
<p>Die restliche Montage war relativ einfach: Die Leiterplatte mit der Lichtschranke und der Elektronik ist mit etwas
|
||||
Heißkleber auf dem Rahmen fixiert. Die Verbindungsdrähte der Beleuchtungs-LED sind so lang, dass die QR-Codes bequem von unten
|
||||
angeleuchtet werden können. Ganz allgemein waren Sorgen bezüglich Bildschärfe und Ausleuchtung unbegründet: Sobald der gesamte
|
||||
QR-Code im Bild ist, funktioniert die Erkennung auch bei bescheidener Bildqualität zuverlässig.</p>
|
||||
|
||||
<p>Bei den drei Schaltern war kein Verkleben nötig, weil sie auch so fest im Gehäuse sitzen. Sehr fest - 12,5 mm Öffnung
|
||||
waren für die 12-mm-Schalter etwas knapp bemessen. Die Kunststoff-Gewinde musste ich etwas abfeilen, damit sie in die Löcher
|
||||
passten. Verbunden wurden sie direkt mit den GPIO-Pins des Raspi per Jumper-Kabel.</p>
|
||||
|
||||
<h2 id="software">Software und Programmierung</h2>
|
||||
|
||||
<h3 id="mocp">mocp statt xmms2</h3>
|
||||
|
||||
<p>
|
||||
Anders als im Vorbild-Projekt sollte als Software-Player nicht <code>xmms2</code> sondern <code>mocp</code> zum Einsatz
|
||||
kommen. Während <code>xmms2</code> die Musiksammlung scannt und die ID3-Tags in einer Datenbank ablegt, über die dann der
|
||||
Zugriff erfolgt, wird <code>mocp</code> einfach der gewünschte Pfad übergeben. Das kann eine mp3-Datei, eine Playlist, ein
|
||||
Verzeichnis oder eine Stream-URL sein. Für meinen Ansatz war <code>mocp</code> besser geeignet, weil nicht sicher war, wie
|
||||
gut die ID3-Tags der Kindermusik-Sammlung gepflegt waren und weil möglichst keine Schreibzugriffe (für die Befüllung der
|
||||
<code>xmms2</code>-Datenbank) auf der SD-Karte nötig sein sollten (dazu später mehr).
|
||||
</p>
|
||||
|
||||
<h3 id="python">Steuerung mit Python 3</h3>
|
||||
|
||||
<p>
|
||||
Im Gegensatz zu den Vorbild-Projekten sollte die Steuerung auch nicht durch <a href="https://github.com/rheikvaneyck/QRMusic">bash-Skripte</a>,
|
||||
sondern mit Python erfolgen. Zunächst mussten die drei Schalter für <em>Vor</em>, <em>Zurück</em> und <em>Play/Pause</em>
|
||||
abgefragt werden. So weit, so simpel. Die einzige "Besonderheit" ist hier, dass nicht, wie <a
|
||||
href="https://www.elektronik-kompendium.de/sites/raspberry-pi/2006051.htm">sonst oft üblich</a>, Pullup-Widerstände eingelötet
|
||||
wurden, sondern die bereits im Raspberry Pi vorhandenen durch das Programm konfiguriert werden:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
# Buttons on PINs 14, 15 and 18
|
||||
GPIO.setup(14, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
GPIO.setup(15, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Die Reaktion auf einen gedrückten Button ist einfach: Der Befehl wird direkt an den <code>mocp</code>-Server weitergereicht.
|
||||
Um Mehrfach-Befehle bei länger gedrücktem Knopf zu vermeiden, wird danach das Skript kurz angehalten.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
if (GPIO.input(15) == False):
|
||||
subprocess.call(["mocp", "--toggle-pause"])
|
||||
time.sleep(BUTTON_PAUSE)
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Zusätzlich muss im Loop noch die Lichtschranke abgefragt werden. Sobald sie eine Unterbrechung meldet, startet der Hauptteil
|
||||
des Programms: Das Licht wird angeschaltet und die Webcam gestartet. Zusätzlich wird ein Scan-Sound abgespielt, damit man auch
|
||||
merkt, dass etwas passiert. Wenn ein QR-Code erkannt wird, wird die entsprechende Playlist oder Stream-URL an den <code>mocp</code>-Server
|
||||
gegeben. Falls es zum Timeout kommt oder der im QR-Code enthaltene Pfad nicht gefunden werden kann, wird ein Fehlersound
|
||||
abgespielt.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Dabei gab es ein paar Klippen zu umschiffen. Zum einen musste der QR-Scanner <code>zbarcam</code> in einem eigenen Prozess
|
||||
gestartet und abgefragt werden. Da zbarcam leider endlos scannt und nicht einfach das erste Ergebnis zurückliefert, muss die
|
||||
Abfrage per Polling erfolgen:
|
||||
</p>
|
||||
|
||||
<pre class="pre-scrollable">
|
||||
# scan QR code
|
||||
zbarcam = subprocess.Popen(['zbarcam', '--nodisplay', '--raw', '-Sdisable', '-Sqrcode.enable', '--prescale=320x240', '/dev/video0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
poll_obj = select.poll()
|
||||
poll_obj.register(zbarcam.stdout, select.POLLIN)
|
||||
|
||||
# wait for scan result (or timeout)
|
||||
start_time = time.time()
|
||||
poll_result = False
|
||||
while ((time.time() - start_time) < QR_SCANNER_TIMEOUT and (not poll_result)):
|
||||
poll_result = poll_obj.poll(100)
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Es gibt zwar auch ein paar Python-Pakete zur Abfrage von <code>zbarcam</code> , aber die waren nicht zur Zusammenarbeit mit
|
||||
Python 3 zu bewegen. Python 3 widerum war nötig, weil ich in Python 2 nicht in der Lage war, das Encoding der von <code>zbarcam</code>
|
||||
zurückgelieferten Strings richtig aufzulösen. Anders als im Artikel von raspberry-pi-geek.de behauptet kann <code>zbarcam</code>
|
||||
nämlich sehr wohl mit Umlauten umgehen. Die werden nur beim Durchreichen über die Konsole zerschossen. Durch das UTF-8
|
||||
default-Encoding von Python 3 kamen auch Pfade mit Umlauten korrekt im Programm an.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Das Skript nutzt vier Sound-Dateien für das akustische Feedback:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="python/scanning.mp3">scanning.mp3</a> wird abgespielt, solange der Scan-Vorgang läuft [<a href="https://www.freesound.org/people/Huminaatio/sounds/159358/">Quelle</a>]</li>
|
||||
<li><a href="python/263133__pan14__tone-beep.mp3">263133__pan14__tone-beep.mp3</a> bestätigt erfolgreiches Hinzufügen zur Playlist (danach startet der Player) [<a href="https://www.freesound.org/people/pan14/sounds/263133/">Quelle</a>]</li>
|
||||
<li><a href="python/159367__huminaatio__7-error.mp3">159367__huminaatio__7-error.mp3</a> zeigt an, dass ein QR-Code nicht erkannt werden konnte (timeout) [<a href="https://www.freesound.org/people/Huminaatio/sounds/159367/">Quelle</a>]</li>
|
||||
<li><a href="python/no.mp3">no.mp3</a> bedeutet, dass der QR-Code zwar erkannt wurde, der enthaltene Link aber nicht in die Playlist übernommen werden konnte (z.B. weil die Datei nicht gefunden wurde) [aus den Sounds des <a href="https://github.com/rheikvaneyck/QRMusic">Vobild-Projekts von rasperry-pi-geek.de</a>]</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Der komplette Code für die Steuerung des Players findet sich in <code style="font-weight: bold;"><a href="https://github.com/tliero/musikrakete/blob/master/code/piplayer_pulse.py">piplayer_pulse.py</a></code>.
|
||||
</p>
|
||||
|
||||
<h2 id="raspi-audio">Audio-Ausgabe beim Raspi: Drama, Baby!</h2>
|
||||
|
||||
<p>
|
||||
Die Musik dann auch wirklich in den JBL zu bekommen war schwieriger als gedacht. Zwar hatte ich <a
|
||||
href="http://www.heise.de/ct/ausgabe/2015-7-Test-Soundkarten-fuer-den-Raspberry-Pi-2563395.html">schon vorher gelesen</a>,
|
||||
dass das, was aus dem Kopfhörerausgang des Raspi kommt, nicht besonders toll ist, aber war davon ausgegangen, dass die Qualität
|
||||
schon ausreichen würde. Dem war nicht so. Der Ausgang knisterte und rauschte durchgängig. Das Audio-Signal musste also auf
|
||||
einem anderen Weg aus dem Computer kommen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Der nächste Ansatz war die Ausgabe über den HDMI-Port. Dafür hatte ich einen <em>HDMI-to-VGA/Audio</em>-Adapter besorgt, der
|
||||
intern wohl auch mit irgendeinem Billig-Chip auf analoges Signal umsetzt. Für die Musikrakete war das zwar schlussendlich nicht
|
||||
die Lösung, aber weil die reine Sound-Ausgabe ohne angehängten Bildschirm beim Raspi etwas hakelig ist, hier noch der Hinweis:
|
||||
Damit der Raspi beim Hochfahren nicht kurzerhand den HDMI-Port lahmlegt weil er kein Display findet, muss in <code>/boot/config.txt</code>
|
||||
die Zeile <code>hdmi_drive=2</code> eingetragen bzw. aktiviert werden (s. <a
|
||||
href="https://www.raspberrypi.org/documentation/configuration/audio-config.md">Raspi-Dokumentation</a>). Zusätzlich muss dann
|
||||
noch die Soundausgabe per <code>raspi-config</code> auf "force HDMI" umgestellt werden. Das funktionierte zunächst einmal sehr
|
||||
gut, aber beim Zusammensetzen gab es ein böse Überraschung. Sobald der HDMI-Adaper am Raspi hing, reichte der vom
|
||||
30-Pin-Anschluss bereitgestellte Strom nicht mehr aus und der Computer hing in einer Boot-Schleife.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Der dritte Versuch war dann die Verwendung einer USB-Soundkarte für unter 2 Euro aus China. Um die Ausgabe über die externe
|
||||
Soundkarte zu erzwingen, wird der interne Chip auf die ALSA-Blacklist gesetzt (<a href="#bash-history">s. Bash History</a>).
|
||||
Damit lief im Test mit angeschlossenen Kopfhörern zunächst alles wunderbar, sobald die Karte über den Adapter an den JBL
|
||||
angeschlossen war, rausche und fiepste es auf einmal wieder. Auch ein Kürzen der Adapterkabel brachte keine Besserung. Nach
|
||||
etwas Verzweiflung fiel auf, dass man Knacksen provozieren konnte, indem man im per SSH zum Raspi verbundenen Terminal eine
|
||||
Taste drückte 0_o
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Offensichtlich verursachte schon die Suche in der bash-History eine Spannugsänderung am Stromanschluss, die im 30-Pin-Adapter
|
||||
als Störsignal auf den Soundanschluss überging. Sobald der Raspberry Pi über ein separates Netzteil versorgt wurde, war die
|
||||
Ausgabe glasklar. Weil die Versorgung über den 30-Pin-Anschluss wesentlich eleganter als ein zusätzliches Netzteil ist,
|
||||
versuchte ich noch, die Störsignale mit Kondensatoren zu filtern. Aber auch die <a
|
||||
href="https://www.youtube.com/watch?v=mk61DNz27FI">hervorragende Erklärung von Blake Jaquot</a> brachte leider nicht das
|
||||
gewünschte Ergebnis.
|
||||
</p>
|
||||
|
||||
<p class="gallery">
|
||||
<a href="fotos/2016-02-22 11-31-26_cut.jpg" class="fancybox" rel="Adapter-Kabel"><img
|
||||
src="fotos/2016-02-22 11-31-26_cut.jpg" width="160"
|
||||
alt="Der 30-Pin-Adapter wurde abisoliert bzw. die Verschalung aufgebrochen und das Audiokabel gekürzt." /></a>
|
||||
<a href="fotos/2016-02-28 13-53-20_cut.jpg" class="fancybox" rel="Adapter-Kabel"><img
|
||||
src="fotos/2016-02-28 13-53-20_cut.jpg" width="160"
|
||||
alt="Der 3,5mm-AUX-Stecker wurde mit kürzeren Drähten neu angebracht, weil ich zunächst nicht sicher war, ob das Rauschen nicht einfach durch Einstreuungen von außen auf den ultra-dünnen und schlecht isolierten Kabeln verursacht wurde. Später kam am USB-Kabel dann noch ein 10-µF-Kondensater zum Filtern der Störspannung hinzu." /></a>
|
||||
<a href="fotos/2016-02-28 13-54-57_cut.jpg" class="fancybox" rel="Adapter-Kabel"><img
|
||||
src="fotos/2016-02-28 13-54-57_cut.jpg" width="160"
|
||||
alt="Auf der Stecker-Seite sollte ein 0,1-µF-Kondensator dem Rauschen ein Ende bereiten. Leider ohne Erfolg." /></a>
|
||||
<a href="fotos/2016-02-28 14-21-15_cut.jpg" class="fancybox" rel="Adapter-Kabel"><img
|
||||
src="fotos/2016-02-28 14-21-15_cut.jpg" width="160"
|
||||
alt="Am Ende wurde das ganze mit Polymorph wieder ordentlich verpackt. Dabei bekam der USB-Stecker auch wieder notwendigen 90-Winkel." /></a>
|
||||
</p>
|
||||
|
||||
<p>Glücklicherweise war ich immerhin vorsichtig genug gewesen, im Gehäuse für genau so einen Fall eine Öffnung für Kabel
|
||||
vorzusehen, so dass zumindest der Anschluss des Zusatz-Netzteils kein Problem war. Natürlich hätte man jetzt auch wieder die
|
||||
Ausgabe per HDMI nutzen können, aber erstens war bei einer Hörprobe kein Unterschied festzustellen (vermutlich sind die
|
||||
Audio-Chips in den Adaptern ohnehin die gleichen) und zweitens hoffe ich immer noch darauf, das Problem mit den Störsignalen
|
||||
irgendwann lösen zu können.</p>
|
||||
|
||||
<p>
|
||||
Zu guter letzt gab es dann noch ein Software-Problem. Manchmal hörte man beim Start eines Tracks statt Musik nur lautes
|
||||
Rauschen. Sobald die Ausgabe einmal mit <code>mocp --toggle-pause</code> unterbrochen und neu gestartet wurde, ging es ganz
|
||||
normal weiter. Abhilfe schaffte die Verwendung eines alternativen Soundsystems an Stelle von ALSA. Nachdem ich PulseAudio
|
||||
installiert und <code>mocp</code> über den Adapter <code>padsp</code> gestartet hatte, hörte sich dann endlich alles an wie
|
||||
gewünscht. (Um den etwas hässlichen Umweg über <code>padsp</code> zu vermeiden müsste man <code>mocp</code> mit <a
|
||||
href="https://moc.daper.net/comment/5240#comment-5240">PulseAudio-Patch neu kompilieren</a>, das habe ich mir bisher gespart.)
|
||||
</p>
|
||||
|
||||
<h2 id="einrichtung">System-Einrichtung</h2>
|
||||
|
||||
<p>Um den Samba-Share automatisch einzubinden, wird in <code>/etc/fstab</code> eine Zeile hinzugefügt:</p>
|
||||
|
||||
<pre>
|
||||
//192.168.1.125/media /media/share cifs guest
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Damit auch das Steuer-Skript automatisch gestartet wird, muss <code>/etc/rc.local</code> ebenfalls um eine Zeile (vor <code>exit
|
||||
0</code>) erweitert werden. Weil die SD-Karte im Raspi später schreibgeschützt werden soll, wird die Log-Ausgabe auf den Share
|
||||
umgeleitet. Dort ist es im Alltag auch besser erreichbar.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
su pi -c 'python3 /home/pi/piplayer.py >> /media/share/piplayer.log 2>&1 &
|
||||
</pre>
|
||||
|
||||
<p>Für die Einrichtung von WLAN, lirc und allen anderen Details hier noch einmal die bereinigte und kommentierte bash
|
||||
History ab Installation von Raspbian Jessie Lite:</p>
|
||||
|
||||
<pre class="pre-scrollable" id="bash-history">
|
||||
# Passwort ändern
|
||||
passwd pi
|
||||
|
||||
# Partition auf SD-Karte vergrößern, Locales anpassen
|
||||
sudo raspi-config
|
||||
|
||||
sudo apt-get update
|
||||
dmesg
|
||||
lsusb
|
||||
sudo apt-get install iw
|
||||
|
||||
# WLAN installieren (Quelle: https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md)
|
||||
# WLANs scannen
|
||||
sudo iwlist wlan0 scan
|
||||
sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
# # Eintrag:
|
||||
# network={
|
||||
# ssid="The_ESSID_from_earlier"
|
||||
# psk="Your_wifi_password"
|
||||
# }
|
||||
|
||||
sudo ifdown wlan0
|
||||
sudo ifup wlan0
|
||||
|
||||
select-editor
|
||||
|
||||
sudo apt-get install ntp
|
||||
sudo /etc/init.d/ntp restart
|
||||
sudo nano /etc/ntp.conf
|
||||
# # in /etc/ntp.conf Zeitserver ergänzen
|
||||
# server de.pool.ntp.org
|
||||
sudo /etc/init.d/ntp restart
|
||||
date
|
||||
|
||||
sudo apt-get install lirc zbar-tools mc moc python3 python-rpi.gpio python3-rpi.gpio
|
||||
|
||||
sudo nano /boot/config.txt
|
||||
# activate lirc and configure PINs by putting the following line in config.txt
|
||||
# Quelle: https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README
|
||||
# dtoverlay=lirc-rpi,gpio_out_pin=22,gpio_in_pin=23
|
||||
|
||||
sudo reboot
|
||||
|
||||
# hiermit kann der Empfang der Fernbedienung getestet werden
|
||||
mode2 -d /dev/lirc0
|
||||
|
||||
# Fernbedienung aufzeichen
|
||||
sudo irrecord -d /dev/lirc0 ~/remote.conf
|
||||
|
||||
# Um die Ausgabe per USB-Soundkarte zu erzwingen, wird der interne Chip auf die ALSA-Blacklist gesetzt
|
||||
# (Quelle: http://www.instructables.com/id/Use-USB-Sound-Card-in-Raspberry-Pi/?ALLSTEPS)
|
||||
# create ALSA blacklist
|
||||
sudo nano /etc/modprobe.d/alsa-blacklist.conf
|
||||
# blacklist snd_bcm2835
|
||||
|
||||
sudo reboot
|
||||
|
||||
# check if driver has been loaded
|
||||
lsmod | grep snd_usb_audio
|
||||
|
||||
# list devices
|
||||
aplay -l
|
||||
arecord -l
|
||||
|
||||
# test audio (funktioniert nicht mit allen Adaptern)
|
||||
speaker-test -Dhw:1,0 -c2 -twav
|
||||
|
||||
# mocp einmal starten und server herunterfahren, um .moc in ~ anzulegen
|
||||
mocp --server
|
||||
mocp --exit
|
||||
|
||||
# Umstellung auf Pulseaudio, um ALSA-Ausraster (Noise/Crackling) zu vermeiden
|
||||
# s. https://moc.daper.net/comment/4316#comment-4316
|
||||
# (für die ganz saubere Lösung müsste man https://moc.daper.net/comment/5240#comment-5240 anwenden)
|
||||
sudo apt-get install pulseaudio
|
||||
nano ~/.moc/config
|
||||
# # Use OSS for Pulseaudio compatibility (run 'padsp mocp')
|
||||
# SoundDriver = OSS:ALSA:JACK
|
||||
# Der moc Server muss jetzt im Skript über padsp gestartet werden, dann läuft alles über pulseaudio:
|
||||
padsp mocp --server
|
||||
|
||||
|
||||
# Einrichtung des Samba-Share vom Fileserver, auf dem die Musik liegt
|
||||
|
||||
# mount smb share
|
||||
sudo mount -t cifs -o user=guest,password= //192.168.1.111/media /media/share
|
||||
|
||||
# Dauerhaft einbinden über Eintrag in /etc/fstab
|
||||
//192.168.1.111/media /media/share cifs guest
|
||||
|
||||
# piplayer.py und Sound-Dateien nach /home/pi kopieren
|
||||
|
||||
# autostart for piplayer with log output redirection
|
||||
# (siehe http://raspberrypi.stackexchange.com/questions/8169/run-startup-application-as-a-specific-user)
|
||||
sudo nano /etc/rc.local
|
||||
# ...
|
||||
# su pi -c 'python3 /home/pi/piplayer.py >> /media/share/Audio/piplayer.log 2>&1 &'
|
||||
# exit 0
|
||||
|
||||
# make logfile accessible (the script is run as pi, the share is accessed as nobody)
|
||||
sudo touch /media/share/piplayer.log
|
||||
sudo chmod 777 /media/share/piplayer.log
|
||||
|
||||
sudo reboot
|
||||
|
||||
# check if running after restart
|
||||
ps -ef | grep python
|
||||
|
||||
# Tests mit QR-Code-Karten
|
||||
tail -f piplayer.log
|
||||
</pre>
|
||||
|
||||
|
||||
<h2 id="qr-karten">Die QR-Code-Karten</h2>
|
||||
|
||||
<p class="gallery" style="padding-left: 1em; float: right;">
|
||||
<a href="images/QR-Code-Vorlage.jpg" class="fancybox" rel="programme"><img
|
||||
src="images/QR-Code-Vorlage.jpg" width="320"
|
||||
alt="Die Dokumenten-Vorlage für die QR-Code-Karten." /></a><br/><br/>
|
||||
<a href="images/QtQR.png" class="fancybox" rel="programme"><img
|
||||
src="images/QtQR.png" width="320"
|
||||
alt="Mit QtQR können die generierten QR-Codes überprüft werden. Das Programm kann Bilddateien einlesen oder von einer angeschlossenen Webcam scannen." /></a>
|
||||
</p>
|
||||
|
||||
<p>Die <a href="QR-Code-Vorlage.odt">Vorlage für die QR-Code-Karten</a> sollte den Bastel-Aufwand möglichst gering halten. Schließlich müssen immer mal wieder
|
||||
neue Karten auf die Schnelle nachprodiziert werden. Deshalb wurden die Karten als Streifen über die volle Breite einer
|
||||
DIN-A4-Seite ausgelegt, so dass man pro Karte nur ein bis zwei Schnitte braucht. Der Streifen wird dann noch einmal gefaltet,
|
||||
so dass der QR-Code auf der Unterseite für die Webcam im Gehäuse sichtbar ist, während man von oben das Album-Cover sehen kann.
|
||||
Weil die Karte am Ende aber auch nicht zu labil sein soll, wird am besten auf Papier mit 160-200 g/m² Stärke ausgedruckt.</p>
|
||||
|
||||
<p>
|
||||
Die Bilder mit den Codes lassen sich online mit <a href="http://goqr.me/">http://goqr.me/</a> oder im Terminal mit <code>qrencode</code>
|
||||
erstellen. Für einen einzelnen Stream bspw. so:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
qrencode -s 6 -m 0 -l L -o Radio-Teddy.png "http://streams.ir-media-tec.com/radioteddy.mp3"
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Vor allem, wenn viele Codes auf einmal erstellt werden sollen, ist <code>qrencode</code> praktisch. Um Codes für alle
|
||||
Verzeichnisse im Kindermusik-Ordner zu erstellen verwendet man einfach eine Schleife:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
cd /media/Audio/Kinder
|
||||
for f in * ; do qrencode -s 6 -m 0 -l L -o "$f".png "$f"; done
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Weil das Encoding einem bei Umlauten und Sonderzeichen schnell in die Quere kommen kann, sollte man das Ergebnis vor dem Druck
|
||||
evtl. noch einmal prüfen. Das kann man gut mit <code>QtQR</code> machen, das eine grafische Oberfläche für <code>zbar</code>
|
||||
und <code>qrencode</code> bietet. Das angezeigte Ergebnis ist also das Selbe, das auch im Player ankommt.
|
||||
</p>
|
||||
|
||||
<h2 id="todo-liste">Was noch zu tun ist</h2>
|
||||
|
||||
<p>
|
||||
Auch wenn die Musikrakete bereits erfolgreich im Einsatz ist, gibt es natürlich noch das ein oder andere zu verbessern. So ist
|
||||
die <strong>Bedienung per Fernbedienung zwar schon vorbereitet, aber das nötige Skript bisher noch nicht erstellt</strong>. Das liegt
|
||||
hauptsächlich daran, dass die Fernbedienung im Alltag bisher noch nicht vermisst wurde - vielleicht kommt das ja noch. Die
|
||||
Umsetzung ist aber denkbar einfach: Die mit <code>lirc</code> aufgezeichneten Tasten müssen nur mit Befehlen für den <code>moc</code>-Server
|
||||
hinterlegt werden.
|
||||
</p>
|
||||
|
||||
<p>Die größte noch verbliebene Kinderkrankeit ist, dass <strong>die Verbindung zum Samba-Share ab und an hängt</strong>. Dann wird der
|
||||
QR-Code zwar erfolgreich gescannt, bis die mp3-Dateien hinzugefügt sind und das Playback startet vergeheb dann aber eine oder
|
||||
zwei Minuten. Eventuell liegt das an einer „eingeschlafenen“ Verbindung. Das kommt zum Glück aber nicht allzu häufig vor und
|
||||
sollte sich auch noch lösen lassen.</p>
|
||||
|
||||
<p>
|
||||
Die größte noch geplante Anpassung ist die <strong>Umstellung der Raspbian-Installation auf <em>read-only</em></strong>. Dadurch wird die SD-Karte
|
||||
geschont und der Player wird immun gegen plötzlichen Verlust der Stromversorgung. Bis jetzt muss der Player vor dem Ausschalten
|
||||
noch per SSH heruntergefahren werden. Für die Umstellung habe ich eine vielversprechende Anleitung im <a
|
||||
href="http://raspberrycenter.de">raspberrycenter.de</a>, die zuletzt aber nur noch über <a
|
||||
href="https://web.archive.org/web/20131206073506/http://raspberrycenter.de/forum/umruestung-raspberry-pi-read-only-root-filesystem">archive.org
|
||||
zu finden war</a>. Einen <a href="https://kofler.info/raspbian-lite-fuer-den-read-only-betrieb/">etwas radikaleren Ansatz
|
||||
findet man bei Michael Kofler</a>. Auch dieser Punkt sollte jedenfalls lösbar sein.
|
||||
</p>
|
||||
|
||||
<p>Der letzte Wermutstropfen ist die oben erwähnte zusätzliche Stromversorgung. Wenn sich doch noch ein Weg finden sollte,
|
||||
<strong>den Raspberry Pi über den 30-Pin-Anschluss mit Strom zu versorgen</strong>, würde es mich freuen. Grundsätzlich geht es da aber eher um
|
||||
die Ingenieurs-Ästhetik - im täglichen Einsatz macht das zusätzliche Netzteil keinen großen Unterschied.</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 700 KiB |
|
After Width: | Height: | Size: 822 KiB |
|
After Width: | Height: | Size: 822 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 889 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 419 KiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 445 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 461 KiB |
|
Before Width: | Height: | Size: 421 KiB |
|
Before Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 323 KiB |
|
Before Width: | Height: | Size: 347 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 375 KiB |
|
Before Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 610 KiB |
|
Before Width: | Height: | Size: 630 KiB |
|
Before Width: | Height: | Size: 653 KiB |
|
Before Width: | Height: | Size: 429 KiB |
|
Before Width: | Height: | Size: 422 KiB |
|
Before Width: | Height: | Size: 425 KiB |
|
Before Width: | Height: | Size: 582 KiB |
|
Before Width: | Height: | Size: 599 KiB |
|
Before Width: | Height: | Size: 622 KiB |
|
Before Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 360 KiB |