001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.tag;
020
021
022import java.awt.Color;
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029
030import lucee.commons.color.ColorCaster;
031import lucee.commons.io.res.Resource;
032import lucee.commons.io.res.type.file.FileResource;
033import lucee.commons.lang.StringUtil;
034import lucee.runtime.PageContext;
035import lucee.runtime.PageSource;
036import lucee.runtime.exp.ApplicationException;
037import lucee.runtime.exp.ExpressionException;
038import lucee.runtime.exp.PageException;
039import lucee.runtime.ext.tag.BodyTagSupport;
040import lucee.runtime.functions.string.JSStringFormat;
041import lucee.runtime.op.Caster;
042import lucee.runtime.op.Decision;
043import lucee.runtime.type.Collection.Key;
044import lucee.runtime.type.Struct;
045import lucee.runtime.video.VideoInput;
046import lucee.runtime.video.VideoInputImpl;
047import lucee.runtime.video.VideoUtilImpl;
048
049import org.apache.commons.collections.map.ReferenceMap;
050
051/**
052 * implementation of the tag Compiler
053 */
054public class VideoPlayerJW extends BodyTagSupport {
055
056        private static final int TYPE_NONE = 0;
057        private static final int TYPE_PLAYLIST = 1;
058        private static final int TYPE_CHAPTERS = 2;
059        
060        private static final int PLAYLIST_NONE = 0;
061        private static final int PLAYLIST_RIGHT = 1;
062        private static final int PLAYLIST_BOTTOM = 2;
063        
064        private static Color BG_COLOR=new Color(51,51,51);
065        private static Color FG_COLOR=new Color(198,198,198);
066        
067        private String video=null;
068        private boolean autostart=false;
069        
070        private lucee.runtime.video.Range showPlay=lucee.runtime.video.Range.TRUE;
071        private lucee.runtime.video.Range showPause=lucee.runtime.video.Range.TRUE;
072        private lucee.runtime.video.Range showTimeline=lucee.runtime.video.Range.TRUE;
073        private List params=new ArrayList();
074        private java.awt.Color bgcolor=BG_COLOR;
075        private java.awt.Color fgcolor=FG_COLOR;
076        private java.awt.Color screencolor=null;
077        private java.awt.Color lightcolor=null;
078        
079        
080        
081        private int width=-1;
082        private int height=-1;
083        private boolean debug;
084        private boolean allowfullscreen;
085        private String strWidth;
086        private String strHeight;
087        private static Map sizes=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT);
088        
089        // JW
090        private Struct passthrough=null;
091        private String preview;
092        
093        private boolean  group=false;
094        private boolean playlistThumbnails=true;
095        private int playlistSize=-1;
096        private int playlist=PLAYLIST_NONE;
097        private String target="_self";
098        private boolean linkfromdisplay;
099        private String overstretch;
100        private boolean download;
101        private String id;
102        private String align;
103        private static int _id=0;
104
105        public VideoPlayerJW()  {
106                
107        }
108        
109        @Override 
110        public void release() {
111                super.release();
112                video=null;
113                autostart=false;
114                
115                showPlay=lucee.runtime.video.Range.TRUE;
116                showPause=lucee.runtime.video.Range.TRUE;
117                showTimeline=lucee.runtime.video.Range.TRUE;
118                params.clear();
119                debug=false;
120                
121                id=null;
122                group=false;
123                playlist = PLAYLIST_NONE;
124                playlistSize=-1;
125                playlistThumbnails=true;
126                target="_self";
127                linkfromdisplay=false;
128                overstretch=null;
129                /*group="yes"
130                        playlist="right,bottom,none"
131                        playlistSize="300"
132                        playlistThumbnails="300"
133                
134                */
135                align=null;
136                
137                
138                bgcolor=BG_COLOR;
139                fgcolor=FG_COLOR;
140                screencolor=null;
141                lightcolor=null;
142                width=-1;
143                height=-1;
144
145                strWidth=null;
146                strHeight=null;
147                
148                // JW
149                passthrough=null;
150                preview=null;
151                allowfullscreen=false;
152                download=false;
153        }
154
155
156
157        protected void setParam(VideoPlayerParamBean param) {
158                params.add(param);
159        }
160
161        /**
162         * @param video the video to set
163         */
164        public void setVideo(String video) {
165                this.video = video;
166        }
167
168        /**
169         * @param autostart the autostart to set
170         */
171        public void setAutostart(boolean autostart) {
172                this.autostart = autostart;
173        }
174
175        /**
176         * @param showPlay the showPlay to set
177         * @throws PageException 
178         */
179        public void setShowplay(String showPlay) throws PageException {
180                this.showPlay = lucee.runtime.video.Range.toRange(showPlay);
181        }
182        public void setId(String id) throws PageException {
183                this.id=Caster.toVariableName(id);
184        }
185
186        /**
187         * @param showPause the showPause to set
188         * @throws PageException 
189         */
190        public void setShowpause(String showPause) throws PageException {
191                this.showPause = lucee.runtime.video.Range.toRange(showPause);
192        }
193
194        /**
195         * @param showTimeline the showTimeline to set
196         * @throws PageException 
197         */
198        public void setShowtimeline(String showTimeline) throws PageException {
199                this.showTimeline = lucee.runtime.video.Range.toRange(showTimeline);
200        }
201
202        /**
203         * @param color the background color to set
204         * @throws PageException 
205         */
206        public void setBgcolor(String color) throws PageException {
207                this.bgcolor = ColorCaster.toColor(color);
208        }
209        public void setBackgroundcolor(String color) throws PageException {
210                setBgcolor(color);
211        }
212        public void setBackground(String color) throws PageException {
213                setBgcolor(color);
214        }
215        public void setScreencolor(String color) throws PageException {
216                this.screencolor = ColorCaster.toColor(color);
217        }
218        public void setLightcolor(String color) throws PageException {
219                this.lightcolor = ColorCaster.toColor(color);
220        }
221        
222
223        /**
224         * @param color the background color to set
225         * @throws PageException 
226         */
227        public void setFgcolor(String color) throws PageException {
228                this.fgcolor = ColorCaster.toColor(color);
229        }
230        
231        public void setForeground(String color) throws PageException {
232                setFgcolor(color);
233        }
234        
235        public void setForegroundcolor(String color) throws PageException {
236                setFgcolor(color);
237        }
238
239        /**
240         * @param width the width to set
241         */
242        public void setWidth(String strWidth) {
243                this.strWidth = strWidth;
244                this.width = Caster.toIntValue(strWidth,-1);
245        }
246
247        /**
248         * @param height the height to set
249         */
250        public void setHeight(String strHeight) {
251                this.strHeight = strHeight;
252                this.height = Caster.toIntValue(strHeight,-1);
253        }
254
255        @Override
256        public int doStartTag() throws PageException {
257                return EVAL_BODY_INCLUDE;
258        }
259        public int doEndTag() throws PageException {
260
261                // fill top video to params
262                if(video!=null) {
263                        VideoPlayerParamBean vppb = new VideoPlayerParamBean();
264                        vppb.setVideo(pageContext,video);
265                        if(!StringUtil.isEmpty(preview))vppb.setImage(pageContext,preview);
266                        params.add(vppb);
267                }
268                else {
269                        if(!StringUtil.isEmpty(preview))
270                                throw new ApplicationException("attribute [preview] is only allowed when attribute [video] is used");
271                }
272                
273                if(params.size()==0)
274                        throw new ApplicationException("you have to define at least one video source");
275                
276                
277                
278                
279                
280                // calculate dimension
281                int[] dim = calculateDimension(pageContext,params,width,strWidth,height,strHeight);
282
283                //print.out(width+":"+height);
284                //print.out(strWidth+":"+strHeight);
285                width=dim[0];
286                height=dim[1];
287                
288                //print.out(width+":"+height);
289                
290                
291                // playlist
292                int dspHeight=-1,dspWidth=-1;
293                if(playlist!=PLAYLIST_NONE) {
294                        if(playlistSize<20)playlistSize=playlist==PLAYLIST_BOTTOM?100:200; 
295                        if(playlist==PLAYLIST_BOTTOM) {
296                                dspHeight=height;
297                                height+=playlistSize;
298                        }
299                        else {
300                                dspWidth=width;
301                                width+=playlistSize;
302                        }
303                }
304                else playlistThumbnails=false;
305                
306                
307                VideoPlayerParamBean param;
308                        
309                
310                String id=getId();//
311                String placeholderId="ph_"+id;
312                String flashId="swf_"+id;
313                
314                StringBuffer sb=new StringBuffer();
315                
316                write(sb,"<script type=\"text/javascript\" src=\"/lucee/swfobject.js.cfm\"></script>");
317                write(sb,"<div ");                   
318                
319                
320                if(passthrough!=null) {
321                        Iterator<Entry<Key, Object>> it = passthrough.entryIterator();
322                        Entry<Key, Object> e;
323                        String key;
324                        while(it.hasNext()) {
325                                e = it.next();
326                                key=e.getKey().getString();
327                                if(StringUtil.startsWithIgnoreCase(key, "div."))
328                                        write(sb,key.substring(4)+"=\""+Caster.toString(e.getValue())+"\" ");
329                        }
330                }
331                write(sb,(align!=null?"align=\""+align+"\"":"")+" id=\""+placeholderId+"\"><a href=\"http://www.macromedia.com/go/getflashplayer\">Get the Flash Player</a> to see this player.</a></div>");                 
332                
333                
334                
335                write(sb,"<script type=\"text/javascript\">\n");                  
336                write(sb,"var so = new SWFObject(\"/lucee/mediaplayer.swf.cfm\", \""+flashId+"\", \""+width+"\", \""+(height)+"\", \"8\", \""+format("#",bgcolor)+"\");\n");                    
337                
338                // script
339                addParam(sb,"allowscriptaccess","always");
340                addVariable(sb,"enablejs","true");
341                addVariable(sb,"javascriptid",flashId);
342                
343                addVariable(sb,"shuffle","false");
344                addVariable(sb,"linktarget",target);
345                addVariable(sb,"linkfromdisplay",Caster.toString(linkfromdisplay));
346                addVariable(sb,"abouttxt","Lucee Video Player");
347                addVariable(sb,"aboutlnk","http://www.lucee.org");
348                
349                // control
350                addParam(sb,"allowfullscreen",Caster.toString(allowfullscreen));
351                addParam(sb,"usefullscreen",Caster.toString(allowfullscreen));
352                addVariable(sb,"autostart",Caster.toString(autostart));
353                if(!StringUtil.isEmpty(overstretch))addVariable(sb,"overstretch",overstretch);
354                addVariable(sb,"showdownload",Caster.toString(download));
355                
356                
357                
358                
359                // color
360                if(lightcolor==null)lightcolor=fgcolor.brighter();
361                if(screencolor==null)screencolor=Color.BLACK;//fgcolor.brighter();
362                addVariable(sb,"backcolor",format("0x",bgcolor));
363                addVariable(sb,"frontcolor",format("0x",fgcolor));
364                addVariable(sb,"lightcolor",format("0x",lightcolor));
365                addVariable(sb,"screencolor",format("0x",screencolor));
366                
367                if(passthrough!=null) {
368                        Iterator<Entry<Key, Object>> it = passthrough.entryIterator();
369                        Entry<Key, Object> e;
370                        String key;
371                        while(it.hasNext()) {
372                                e = it.next();
373                                key=e.getKey().getString();
374                                if(StringUtil.startsWithIgnoreCase(key, "param."))
375                                        addParam(sb,key.substring(6),Caster.toString(e.getValue()));
376                                else if(StringUtil.startsWithIgnoreCase(key, "variable."))
377                                        addVariable(sb,key.substring(9),Caster.toString(e.getValue()));
378                                else if(StringUtil.startsWithIgnoreCase(key, "div."));
379                                else
380                                        addVariable(sb,key,Caster.toString(e.getValue()));
381                        }
382                }
383
384                if(params.size()>1 && group) addVariable(sb,"repeat","true");
385                
386                /*if(playlist!=PLAYLIST_NONE) {
387                        if(playlistSize<20)playlistSize=playlist==PLAYLIST_BOTTOM?300:200; 
388                        if(playlist==PLAYLIST_BOTTOM) {
389                                addVariable(sb,"displayheight",Caster.toString(height));
390                                height+=playlistSize;
391                        }
392                        else {
393                                addVariable(sb,"displaywidth",Caster.toString(width));
394                                width+=playlistSize;
395                        }
396                        if(playlistThumbnails && hasImages())addVariable(sb,"thumbsinplaylist","true");
397                }*/
398
399                // dimension
400                if(dspWidth>0)addVariable(sb,"displaywidth",Caster.toString(dspWidth));
401                if(dspHeight>0)addVariable(sb,"displayheight",Caster.toString(dspHeight));
402                addVariable(sb,"width",Caster.toString(width));
403                addVariable(sb,"height",Caster.toString(height));
404                if(playlistThumbnails && hasImages())addVariable(sb,"thumbsinplaylist","true");
405                
406                //if(preview!=null) addVariable(sb,"image",toPath(preview));
407                //Iterator it = params.iterator();
408                //addVariable("file","/rvp/videos/David.flv");
409                //addVariable("captions","http://localhost:8080/caption.cfm");
410                //while(it.hasNext()) {
411                        //param=(VideoPlayerParamBean) it.next();
412                        //addVariable(sb,"file",toPath(param.getResource()));
413                        //break;        
414                //}
415                //addVariable("image","video.jpg");
416                
417                write(sb,"so.write(\""+placeholderId+"\");\n");
418                //if(params.size()>1) {
419                Iterator it = params.iterator();
420                while(it.hasNext()) {
421                        param=(VideoPlayerParamBean) it.next();
422                        addItem(sb,flashId,param);
423                }
424                //}
425                write(sb,"</script>");
426                try {
427                        if(debug) {
428                                pageContext.forceWrite("<pre>"+StringUtil.replace(sb.toString(), "<", "&lt;", false)+"</pre>");
429                        }
430                        pageContext.forceWrite(sb.toString());
431                        
432                        
433                } 
434                catch (IOException e) {
435                        
436                }
437            return EVAL_PAGE;
438        }
439
440        private synchronized String getId() {
441                if(!StringUtil.isEmpty(id)) return id;
442                _id++;
443                if(_id<0) _id=1;
444                return ""+_id; 
445        }
446
447        private boolean hasImages() {
448                Iterator it = params.iterator();
449                while(it.hasNext()) {
450                        if(((VideoPlayerParamBean) it.next()).getImage()!=null) return true;
451                }
452                return false;
453        }
454
455        private void addItem(StringBuffer sb, String id, VideoPlayerParamBean param) {
456                //sb.append("setTimeout('thisMovie(\""+id+"\").addItem({file:\""+JSStringFormat.invoke(path)+"\"},null);',1000);\n");
457                
458                // file
459                String file = "file:'"+JSStringFormat.invoke(toPath(param.getResource()))+"'";
460                
461                // image
462                String image="";
463                if(param.getImage()!=null) {
464                        image=",image:'"+JSStringFormat.invoke(toPath(param.getImage()))+"'";
465                }
466                
467                // title
468                String title="";
469                if(!StringUtil.isEmpty(param.getTitle())) {
470                        title=",title:'"+JSStringFormat.invoke(param.getTitle())+"'";
471                }
472                
473                // link
474                String link="";
475                if(!StringUtil.isEmpty(param.getLink())) {
476                        link=",link:'"+JSStringFormat.invoke(param.getLink())+"'";
477                }
478                
479                // author
480                String author="";
481                if(!StringUtil.isEmpty(param.getAuthor())) {
482                        author=",author:'"+JSStringFormat.invoke(param.getAuthor())+"'";
483                }
484                
485                sb.append("addItem('"+id+"',{"+file+title+image+link+author+"});\n");
486        }
487
488        private void addVariable(StringBuffer sb, String name, String value) {
489                value=JSStringFormat.invoke(value);
490                if(!(value.equals("false") || value.equals("true")))
491                        value="'"+value+"'";
492                sb.append("so.addVariable('"+JSStringFormat.invoke(name)+"',"+value+");\n");
493        }
494
495        private void addParam(StringBuffer sb,String name, String value) {
496                sb.append("so.addParam('"+name+"','"+value+"');\n");
497        }
498
499        private static int[] calculateDimension(PageContext pc,List params,int width, String strWidth,int height, String strHeight) throws PageException {
500                Iterator it = params.iterator();
501                ArrayList sources=new ArrayList();
502                //Resource[] sources=new Resource[params.size()];
503                VideoPlayerParamBean param;
504                
505                while(it.hasNext()) {
506                        param = (VideoPlayerParamBean) it.next();
507                        if(param.getVideo()!=null)
508                                sources.add(new VideoInputImpl(param.getVideo()));
509                }
510                return VideoUtilImpl.getInstance().calculateDimension(pc, (VideoInput[])sources.toArray(new VideoInput[sources.size()]), width, strWidth, height, strHeight);
511                
512        }
513
514        private String toPath(Resource res) {
515                        if(!(res instanceof FileResource)) return res.getAbsolutePath();
516                        
517                        //Config config=pageContext.getConfig();
518                        PageSource ps = pageContext.toPageSource(res,null);
519                        if(ps==null) return res.getAbsolutePath();
520                        
521                        String relPath = ps.getRealpath();
522                        relPath=relPath.replace('\\', '/');
523                        if(relPath.endsWith("/"))relPath=relPath.substring(0,relPath.length()-1);
524                        
525                        //print.out("real:"+relPath);
526                        String mapping=ps.getMapping().getVirtual();
527                        mapping=mapping.replace('\\', '/');
528                        if(mapping.endsWith("/"))mapping=mapping.substring(0,mapping.length()-1);
529                        
530                        return mapping+relPath;
531                
532                
533        }
534
535
536
537        private void write(StringBuffer sb, String string) {
538                sb.append(string);
539        }
540
541
542
543        private static String format(String prefix, Color color) {
544                return prefix+toHex(color.getRed())+toHex(color.getGreen())+toHex(color.getBlue());
545        }
546
547
548
549        private static String toHex(int value) {
550                String str = Integer.toHexString(value);
551                if(str.length()==1) return "0".concat(str);
552                return str;
553        }
554
555        /**
556         * @param debug the debug to set
557         */
558        public void setDebug(boolean debug) {
559                this.debug = debug;
560        }
561
562        /**
563         * @param passthrough the passthrough to set
564         */
565        public void setPassthrough(Struct passthrough) {
566                this.passthrough = passthrough;
567        }
568
569        /**
570         * @param preview the preview to set
571         * @throws ExpressionException 
572         */
573        public void setPreview(String preview) {
574                this.preview = preview;//ResourceUtil.toResourceExisting(pageContext, preview);
575        }
576
577        /**
578         * @param allowfullscreen the allowfullscreen to set
579         */
580        public void setAllowfullscreen(boolean allowfullscreen) {
581                this.allowfullscreen = allowfullscreen;
582        }
583        
584        public void setAlign(String strAlign) throws ApplicationException {
585                if(StringUtil.isEmpty(strAlign)) return;
586                strAlign=strAlign.trim().toLowerCase();
587                if("right".equals(strAlign)) this.align = "right";
588                else if("center".equals(strAlign)) this.align = "center";
589                else if("left".equals(strAlign)) this.align = "left";
590                else 
591                        throw new ApplicationException("invalid value for attribute align ["+strAlign+"], valid values are [left,center,right]");
592        }
593
594        /**
595         * @param group the group to set
596         */
597        public void setGroup(boolean group) {
598                this.group = group;
599        }
600        public void setLinktarget(String target)  {
601                this.target = target;
602        }
603        public void setTarget(String target)  {
604                this.target = target;
605        }
606        public void setLinkfromdisplay(boolean linkfromdisplay)  {
607                this.linkfromdisplay = linkfromdisplay;
608        }
609
610        /**
611         * @param playlistThumbnails the playlistThumbnails to set
612         */
613        public void setPlaylistthumbnails(boolean playlistThumbnails) {
614                this.playlistThumbnails = playlistThumbnails;
615        }
616        public void setThumbnails(boolean playlistThumbnails) {
617                setPlaylistthumbnails(playlistThumbnails);
618        }
619        public void setThumbs(boolean playlistThumbnails) {
620                setPlaylistthumbnails(playlistThumbnails);
621        }
622
623        /**
624         * @param playlistSize the playlistSize to set
625         */
626        public void setPlaylistsize(double playlistSize) throws ApplicationException {
627                if(playlistSize<=40) throw new ApplicationException("playlist size has to be a positive number, at least 41px");
628                this.playlistSize = (int) playlistSize;
629        }
630
631        /**
632         * @param playlist the playlist to set
633         */
634        public void setPlaylist(String strPlaylist) throws PageException {
635                strPlaylist=strPlaylist.trim().toLowerCase();
636                if("right".equals(strPlaylist))                 playlist=PLAYLIST_RIGHT;
637                else if("bottom".equals(strPlaylist))   playlist=PLAYLIST_BOTTOM;
638                else if("none".equals(strPlaylist))     playlist=PLAYLIST_NONE;
639                else if(Decision.isBoolean(strPlaylist)) {
640                        playlist=Caster.toBooleanValue(strPlaylist)?PLAYLIST_BOTTOM:PLAYLIST_NONE;
641                }
642                else throw new ApplicationException("invalid playlist definition ["+strPlaylist+"], valid values are [right,bottom,none]");
643        }
644
645        /**
646         * @param overstretch the overstretch to set
647         */
648        public void setOverstretch(String overstretch) throws PageException {
649                overstretch=overstretch.trim().toLowerCase();
650                if("fit".equals(overstretch))           overstretch="fit";
651                else if("none".equals(overstretch)) overstretch="none";
652                else if("proportion".equals(overstretch)) overstretch="true";
653                else if(Decision.isBoolean(overstretch)) {
654                        overstretch=Caster.toString(Caster.toBooleanValue(overstretch));
655                }
656                else throw new ApplicationException("invalid overstretch definition ["+overstretch+"], valid values are [fit,none,true,false]");
657                
658                this.overstretch = overstretch;
659        }
660
661        /**
662         * @param download the download to set
663         */
664        public void setDownload(boolean download) {
665                this.download = download;
666        }
667        
668}